はじめに
あとで読みたい記事、こんなことありませんか?
- とりあえずURLだけ保存して埋もれる
- ブックマークしても見返さない
- YouTubeもnoteもバラバラ
LINEに送るだけで管理できる仕組みを作りました。
作ったもの
LINEにURLを送るだけで
- 保存が一瞬で終わる
- 見返すきっかけが自動で来る
- 読み終わったら一言で整理できる
ようにしました。
完成イメージ
登録
LINEで下記のようにURLを送るだけです。
https://note.com/xxx
↓
するとスプレッドシートに登録され、下記のようなメッセージが返信されます。
登録しました。
管理番号:001
登録日時:2026/04/10 19:30:00
URL:https://note.com/xxx
完了したら「001完了」と送ってください。
定期通知
毎日12時に、対象URLのリマインドがLINEに届きます。
こちらどうですか?
登録日時:2026/04/10 19:30:00
管理番号:001
URL:https://note.com/xxx
読み終わったら「001完了」と送ってください。
完了処理
URLを読み終わって管理対象から外したい場合は、下記のように「管理番号 + 完了」と送ります。
001完了
↓
すると登録情報のステータスが更新され、完了メッセージが返信されます。
更新されたURLは、以後リマインドの対象外になります。
完了に更新しました。
管理番号:001
完了日時:2026/04/10 21:00:00
使用技術
今回使用した技術は以下の通りです。
- n8n
- LINE Messaging API
- Google Sheets
全体構成
LINE → Webhook → n8n
↓
・URLなら登録
・完了なら更新
↓
Google Sheetsに保存または更新
↓
LINE返信
+
Schedule Trigger → ランダム通知
スプレッドシート設計
| 管理番号 | URL | 登録日時 | ステータス | 完了日時 |
|---|---|---|---|---|
| 001 | https://... | 2026/04/10 19:30:00 | todo |
実装(n8n)
① LINE受信
Webhookで受信したLINEイベントから、必要な情報だけを取り出します。
ポイント
replyTokenuserIdtext
を取り出す
const event = $json.body?.events?.[0] ?? $json.events?.[0];
if (!event) {
return [];
}
return [{
json: {
replyToken: event.replyToken ?? "",
userId: event.source?.userId ?? "",
text: (event.message?.text ?? "").trim()
}
}];
② URL or 完了の判定
受信したテキストがURL登録なのか、完了更新なのかを判定して後続処理を分岐します。
const text = ($json.text || "").trim();
const urlMatch = text.match(/https?:\/\/[^\s]+/i);
const completeMatch = text.match(/^(\d{1,})\s*完了$/);
let mode = "unknown";
let url = "";
let managementId = "";
if (urlMatch) {
mode = "register";
url = urlMatch[0];
} else if (completeMatch) {
mode = "complete";
managementId = completeMatch[1].padStart(3, '0');
}
return [{
json: {
...$json,
mode,
url,
managementId
}
}];
③ Switchノード
- register → 登録フロー
- complete → 完了フロー
▼ 登録フロー
④ 登録済み情報取得(Google Sheets)
登録済みの情報を検索条件なしで全件取得します。
このあと管理番号を連番で採番するために使います。
⑤ 登録情報作成(Codeノード)
const items = $input.all();
let max = 0;
for (const item of items) {
const num = Number(item.json["管理番号"]);
if (Number.isFinite(num) && num > max) {
max = num;
}
}
const next = max + 1;
const managementId = String(next).padStart(3, '0');
const registeredAt = new Date().toLocaleString("ja-JP", {
timeZone: "Asia/Tokyo",
hour12: false
});
return [{
json: {
managementId,
url: $('URL or 完了の判定').first().json.url,
registeredAt,
status: "todo",
doneAt: ""
}
}];
⑥ スプレッドシートに保存
スプレッドシートの下記カラムに情報を登録します。
- 管理番号
- URL
- 登録日時
- ステータス = todo
⑦ LINE返信
{
"replyToken": "{{ $('URL or 完了の判定').item.json.replyToken }}",
"messages": [
{
"type": "text",
"text": "登録しました。\n管理番号:{{$json.managementId}}\n登録日時:{{$json.registeredAt}}\nURL:{{$json.url}}\n完了したら「{{$json.managementId}}完了」と送ってください。"
}
]
}
▼ 完了フロー
⑧ 管理番号を条件に対象データを取得(Google Sheets)
完了メッセージで送られてきた管理番号をもとに、対象の行を取得します。

⑨ 更新情報作成(Codeノード)
const input = $input.first().json;
const doneAt = new Date().toLocaleString("ja-JP", {
timeZone: "Asia/Tokyo",
hour12: false
});
return [{
json: {
...input,
status: "done",
doneAt
}
}];
⑩ ステータス更新
管理番号を条件にスプレッドシートの下記カラムを更新します。
- ステータス → done
- 完了日時
⑪ LINE返信
{
"replyToken": "{{ $('URL or 完了の判定').item.json.replyToken }}",
"messages": [
{
"type": "text",
"text": "完了に更新しました。\n管理番号:{{$json.管理番号}}\n完了日時:{{$json.doneAt}}"
}
]
}
▼ 定期通知フロー
⑫ Schedule Trigger
Schedule Triggerで毎日12時に実行します。
⑬ todoのみ取得(Google Sheets)
⑭ ランダム抽出(Codeノード)
const items = $input.all();
if (!items.length) {
return [];
}
const randomIndex = Math.floor(Math.random() * items.length);
return [items[randomIndex]];
⑮ LINE push送信
ランダム抽出したURLを、指定の文言でリマインドします。
{
"to": "ユーザーID",
"messages": [
{
"type": "text",
"text": "こちらどうですか?\n登録日時:{{$json['登録日時']}}\n管理番号:{{$json['管理番号']}}\nURL:{{$json['URL']}}"
}
]
}
工夫したポイント
① 管理番号は短く(重要)
長いIDは打ち込むのがめんどくさいため
001
002
003
にしました。
② UX最優先
ユーザー操作はこれだけ
- URL送る
-
001完了と送る
③ ランダム通知で「見る機会」を作る
ただ保存するだけだと見ないので定期的にリマインドするようにしました
ハマりポイント
JSONエラー
LINE APIはJSONが厳密なので
- ダブルクォート抜け
- カンマ抜け
ですぐエラーになります。
まとめ
- ユーザーの操作をシンプルにすることで、使いやすくしました。
- 複数のサービスのURLを一元管理でき、自動リマインドによって見返すきっかけも作れます
おまけ(今後やりたい)
シンプルで最低限を心がけましたが、下記の追加機能はあってもよいと感じました。
- 「未完了一覧」コマンド
- 重複URLチェック





