晚上八點,系統的排程介面顯示「投資晨報:執行成功 ✅」,容器的 log 翻到底沒有半行紅字,甚至 Telegram API 回傳的也是 HTTP 200 OK。
但我的手機螢幕就是沒亮,該跳出來的訊息硬生生消失了。
這種錯最磨人——不是會在你面前爆炸的崩潰,而是一切看起來都對,結果什麼都沒發生。我為了一則消失的投資晨報,查了整整三天。也是因為這個坑,我學到一件事:通訊層「建立」只是一半,另一半是「測試」——因為它的失敗是無聲的,你不主動驗證就不會知道它壞了。
今天就照「建立 → 測試」走一遍。
在台灣問這個問題很合理——身邊的人都用 LINE,做一個 LINE 官方帳號來推播看起來更自然。我也真的先做了。
卡住我的是免費額度的推播則數上限。
LINE 官方帳號的免費方案,每個月能主動推播的訊息數是有上限的;超過就要升級付費方案。對一般的商業帳號來說這很合理——你推播是為了做生意。但我的用途完全相反:
這些全部都是主動推播,而且我希望「該叫的時候一定要叫得出來」。一個會因為額度用完而安靜的告警系統,比沒有告警更危險——這正是後面 Day 20 監控篇的核心主題。
所以取捨很清楚:**我需要的是推播無上限、而非互動體驗最好的通道。**Telegram 的 Bot API 在這一點上完全免費、沒有則數限制(只有頻率限制,見文末),這對一個每天要推幾十則的個人系統來說是決定性的。
補充一個公平的說法:如果你的用途是「偶爾通知一下」,LINE 官方帳號的免費額度很可能夠用,而且家人不用另外裝 App。選通道要看你推播的頻率,不是看哪個比較多人用。
架構上,六個 Agent 每天會產出不同類型的訊息:投資晨報、家居警示、備考提醒、生活規劃……全部塞進同一個對話視窗會變成洗版災難。
解法是用 Telegram 的群組 + Topic(主題)——一個群組底下開多個 Topic 分頻道,每個 Agent 各走各的:
| Topic ID | 頻道用途 | 對應 Job 類型 |
|---|---|---|
2 |
投資 | 盤前/收盤推播、持倉異動告警 |
4 |
碩士備考 | 進度提醒、截止倒數 |
29 |
命理 | 每日能量檢查 |
97 |
智慧家庭 | 環境感知、設備離線告警 |
102 |
程式開發 | 開發看板、部署驗收 |
103 |
生活規劃 | 週行程、每日收尾回顧 |
⚠️ 群組 ID 已遮蔽(文中一律寫成
-100XXXXXXXXXX)。Topic 編號是實際值,但它單獨沒有用——沒有群組 ID 和 Bot Token,任何人都打不進來。這裡列真值是為了讓後面「哪個 Job 該去哪個頻道」的例子對得起來。
概念很乾淨:Job 設定裡指定 chat_id(群組)和 thread_id(Topic),訊息就會落到對的頻道。魔鬼藏在「怎麼建」跟「怎麼確認它真的動了」。
這件事我一開始沒搞懂,白繞了不少路——Bot 的「發」和「收」用的是完全不同的機制,而且對網路的要求天差地遠:
| 方向 | 怎麼運作 | 需要對外開東西嗎 | |
|---|---|---|---|
| 發訊息 | 我的系統 → Telegram | 對 Telegram 的 API 發一個 HTTPS 請求 | 完全不用——這是我主動打出去的 |
| 收訊息 | Telegram → 我的系統 | 兩種選擇(見下) | 要,或者要一直輪詢 |
收訊息的兩種做法:
我選的是 webhook。而註冊 webhook 時可以一併設一組 secret token——之後 Telegram 每次 POST 都會帶著它,我這邊驗證這組值,才知道「這真的是 Telegram 打來的,不是別人假冒」。對外開一個端點,就一定要有辦法驗證來源。
這個不對稱有個實際的好處,也解釋了明天那篇的必要性:
如果我只要推播,家裡的網路什麼都不用動。
是因為我想「跟它對話」,才需要一個對外可達的入口。
換句話說,Day 6 那條 Cloudflare Tunnel 只服務「收」這個方向。如果你的需求純粹是「系統有事通知我」,可以完全跳過那一篇——這也是我建議大家從推播開始做的原因:先享受單向的好處,需要雙向時再處理網路。

實際串接沒幾步,但最後「查 ID」那步最容易卡住,先把流程走一遍:
@BotFather,輸入 /newbot 一路建到底,拿到一組 Bot Token——這把就是對外發訊的鑰匙,只進 .env、不進 git。chat_id 與 thread_id:這兩個值介面上看不到,得用 Bot API 撈。在目標 Topic 隨手發一則訊息,呼叫 https://api.telegram.org/bot<TOKEN>/getUpdates,回應裡的 chat.id 就是群組 ID(負號大數字),message_thread_id 就是那個 Topic 的 ID。chat_id 和各 Topic 的 thread_id 填進每個 Job 的 delivery 設定。查
thread_id還有個偷吃步:Telegram 桌面版對著 Topic「複製連結」,網址最後一段數字就是 thread_id。
接好之後,先別急著上線——通訊層的失敗是無聲的,建立完一定要測。
通訊層最反直覺的一點:API 回你 ok: true,不代表訊息真的出現在頻道裡。 一般程式錯了會丟例外,Telegram 這裡很多失敗是「成功地什麼都沒做」。所以建好之後第一件事不是上線,是測試——而且要人眼確認。我的三步測試法:
測試時你會踩到的,正好是這三個典型的坑——把它們當成上線前的檢查清單:
第一次串接時我把群組 ID 直接填數字進去,結果 API 回 chat not found。Telegram 的群組(supergroup)ID 是負數大數字(-100XXXXXXXXXX),個人對話才是正數。少了那個負號,API 就當你在找一個不存在的個人聊天室。至少它會出聲——後面兩個就不會了。
topic:1(General)的靜默失敗群組剛建好時,預設會有一個「General(通用)」主題,它的 thread_id 是 1。我很自然地把第一個 Job 指到 topic:1,結果狀態 ✅、log 無錯誤,訊息卻沒出現。
一開始我以為是 Docker 網路或 DNS 出問題。為了排除框架的干擾,我退回最原始的工具,直接用 curl 手動打 API:
curl -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
-d "chat_id=-100XXXXXXXXXX" \
-d "message_thread_id=1" \
-d "text=Hello Test"
猜猜 API 回了什麼?{"ok":true,"result":{...}}。Telegram 明確告訴我「成功送達」,但手機依然毫無動靜。查了無數論壇才搞懂:Bot 無法以 thread_id=1 發文到 General 主題——這是 Telegram 端的限制,但 API 不報錯,默默把訊息丟進黑洞,回你一個大大的 true。
這就是「靜默失敗」最危險的地方:監控全部綠燈,但功能其實是壞的。 教訓一句話:任何 Job 都不准用 topic:1。
修掉 topic:1 之後,命理頻道的能量檢查又消失了。這次是 Job 指到 topic:3,但這個 Topic 根本不存在(我中途調整頻道時刪掉了舊的)。照理說該回 404 或 Bad Request,但 Telegram 一樣靜默吞掉訊息、回傳 ok:true。改指到實際存在的 topic:29 才恢復。
三個坑合起來一句話:Telegram 對「無效的 thread_id」一律靜默吞掉。你不能靠 API 回應判斷訊息有沒有送出去——只能靠主動測試。
查了三天的代價,總要換成制度。既然 API 靠不住,那就在 Job 進入系統前先擋下無效設定。我在排程層做了一個「品質閘道(Quality Gate)」,任何要新增或修改的推播任務都得先過這層驗證:
// 品質閘道:Telegram Topic 防呆驗證
const VALID_TOPIC_IDS = [2, 4, 29, 97, 102, 103];
function validateTelegramDelivery(config) {
const topicId = parseInt(config.thread_id, 10);
if (topicId === 1) {
throw new Error("🚨 [品質閘道拒絕] 嚴禁使用 topic:1 (General),會導致靜默失敗!");
}
if (!VALID_TOPIC_IDS.includes(topicId)) {
throw new Error(`🚨 [品質閘道拒絕] 未知的 Topic ID: ${topicId}。請確保頻道已建立並加入白名單。`);
}
return true;
}
topicId 不在白名單就在建立階段直接拋 Error 擋下——我把 Telegram 那個「不報錯」的行為,用自己的閘道補成「會大聲報錯」。這是自架系統很核心的心法:當底層工具會靜默失敗,你的責任就是在上層幫它補一道會出聲的防線。
既然講到 Telegram,再補兩個自架時一定會遇到的雷點:
1. 單則訊息 4096 字元上限
有時候投資長分析財報太熱情,回傳一大篇 Markdown,API 就報 MESSAGE_TOO_LONG。Telegram 單則訊息上限是 4096 字元。解法是在 Gateway 層加攔截器:超長就自動切塊(chunking)分多次發,或強制截斷並附上「…(字數超過,請至 md-server 查看)」。
2. 頻率限制(Rate Limit, HTTP 429)
如果六個 Agent 都設在早上 8:00 準時發晨報,有機率觸發 429 Too Many Requests(同群組廣播大約每分鐘 20 則上限)。排程器要加重試機制(Exponential Backoff),被擋了就等幾秒再傳,別 fire-and-forget。
通訊層的功課,一半在「建立」、一半在「測試」:
getUpdates 撈 chat_id / thread_id → 填進 Job。topic:1、不存在的 thread_id——Bot 發文會靜默失敗(回成功但訊息消失)。靜默失敗是維運自架系統最該警惕的一類錯——它不會吵你,只會讓你在最需要那則訊息時,發現它從來沒來過。所以通訊層沒有「建好就好」,只有「測過才算數」。
明天談對外服務:怎麼讓家裡的 NAS 對公網提供服務,但路由器一個 port 都不用開——Cloudflare Tunnel。
🔑 這篇的關鍵字
選通道:LINE 官方帳號免費方案有推播則數上限(告警用途不適合)· Telegram Bot API 推播免費無則數限制
收發兩條路:發=對 API 發 HTTPS 請求(不需對外開埠)· 收=webhook(要公網 HTTPS 端點 + secret token 驗來源)或 long polling(不需對外但要輪詢)
建立:@BotFather·/newbot· supergroup + Topics · Bot 需設為管理員 ·getUpdates撈chat.id與message_thread_id
必踩的坑:群組 ID 是負數(-100...)·thread_id=1(General)Bot 無法發文且不報錯 · 不存在的 thread_id 一樣靜默吞掉
限制:單則 4096 字元(MESSAGE_TOO_LONG)·429 Too Many Requests→ Exponential Backoff
防呆:把有效 Topic 白名單寫進建立階段的驗證,別依賴 API 回應
我是一名金融業資訊工程師,這是我半年來在家自架 AI Agent 系統的實錄。