← 返回網誌

SMS DLR 狀態碼完全解讀 — SMD、DELIVRD、UNDELIV、REJECTD 到底代表什麼

做過 SMS 整合的人都遇過這個場景:API 回了 rc: 100,報表上顯示「成功」,但客戶說沒收到。

問題出在一個很多人沒分清楚的地方 — 「我們收下了」「我們送出去了」「客戶手機收到了」是三件不同的事,而很多系統把它們當成同一件。

這篇把 SMS 的回報機制拆開來講。

三個時間點,不是一個

一則短訊從你的伺服器到客戶手機,中間至少有三個可觀察的狀態:

  1. 同步回應 — 你 POST 之後立刻拿到的回覆。代表平台收下請求並排入佇列。
  2. 第一段 callback(送出請求回報) — 請求是否通過驗證並提交給電信商。這一段決定是否計費。
  3. 第二段 callback(送達回報 / DLR) — 電信商回報收件人裝置的最終狀態。

三個時間點可能相隔數秒到數小時。如果你的報表只記錄第 1 個,那張報表基本上只在告訴你「我送出了幾個 HTTP 請求」。

同步回應:只代表收下了

{ "rc": 100, "rm": "OK", "ref_id": "145737475" }

rc: 100 代表請求格式正確、已接受。ref_id 是這次請求的識別碼 — 後面兩段 callback 都會帶同一個 ref_id,這是串起整條鏈的鑰匙。

如果請求本身有問題(例如缺了必填欄位),這裡就會直接回 rc: -100,並在 rm 說明原因。

第一段 callback:提交結果

這一段告訴你請求有沒有真的交到電信商手上。

成功(rc = 100,會計費):

狀態意思
SMD有效發送,訊息已提交電信商等待派送

失敗(rc = -100,不計費):

狀態意思通常是什麼問題
IM地區號碼或手機號碼不正確號碼格式沒清理乾淨,帶了 +、空格或前綴
OFCA號碼在拒收訊息登記冊中,已被過濾(香港)對方登記了拒收,且你沒有其同意
IUL號碼在拒收名單中,未發送該號碼之前已退訂
IP點數不足帳戶餘額用完
IS發送人名稱無效或未登記Sender ID 沒在你的帳戶上完成綁定

這五個失敗碼有一個共同點:它們都是你這邊可以事前避免的。 號碼清洗、退訂名單管理、餘額監控、Sender ID 綁定,全部在你的控制範圍內。

值得特別注意 IP:如果你的系統沒有監控餘額,大批量發送到一半沒點數,剩下的全部靜靜失敗。建議在發送前呼叫 /v1/acct/get-points-balance 檢查一次。

第二段 callback:真正的送達回報

電信商回報最終結果時才會有這一段。

狀態意思建議處理
DELIVRD已送達收件人裝置這才是「送到了」
EXPIRED超過有效期仍未送達重試前先確認號碼是否仍在使用
UNDELIV目的地不可達、網絡問題,或裝置關機可以稍後重試,成功率不低
REJECTD內容無效、列入黑名單,或政策限制不要原樣重送 — 改內容或查發送人設定
UNKNWN電信商無法判定最終狀態不宜當失敗直接重發,先觀察比例

三個最常被誤處理的狀態

UNDELIV 被當成永久失敗。 這多半是暫時性的 — 關機、隧道裡、出國漫遊。直接把號碼標成無效會白白損失名單。合理做法是隔一段時間重試一到兩次。

REJECTD 被自動重送。 這是最浪費點數的做法。內容或政策問題不會因為再送一次就消失,只會再被拒一次。收到這個狀態應該告警給人看,不是丟進重試佇列。

UNKNWN 被算進失敗率。 這會讓你的送達率數字看起來比實際差。比較合理的做法是單獨統計它的比例 — 如果某條路由的 UNKNWN 比例明顯偏高,那本身就是一個訊號。

怎麼用這些資料對帳

ref_id 串起三個數字:

  • 你送出的請求數
  • 第一段回 SMD 的筆數 ← 這是計費基準
  • 第二段回 DELIVRD 的筆數

三個數字之間的落差,就是你真正該關心的東西。另外可以用 /v1/sms/get-usage-report 以日期區間取回 sms_countpoints_used,和自己的紀錄互相驗證。

如果一家供應商只給你「送出成功」一個數字,你其實無從判斷中間發生了什麼 — 這也是為什麼我們把每個失敗碼原樣轉給客戶,而不是包裝成一個好看的百分比。詳見誠實送達

實作上的兩個提醒

Callback 要寫成冪等的。ref_id + status 作為唯一鍵。同一則 callback 有可能重複送達,如果你的處理邏輯會扣庫存或發郵件,重複執行就出事了。

Callback URL 要先在後台設定。 沒設定的話這兩段回報根本不會發出來 — 很多人以為是自己的 endpoint 有問題,其實是根本沒開。

完整的欄位定義與範例 payload 在 SMS API 整合指南