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

本文目錄
  1. 三個時間點,不是一個
  2. 同步回應:只代表收下了
  3. 第一段 callback:提交結果
  4. 第二段 callback:真正的送達回報
  5. 怎麼用這些資料對帳
  6. 實作上的兩個提醒

重點摘要

  • 同步回應 rc: 100 只代表平台收下請求並排入佇列,不代表客戶手機收到了。
  • 第一段回 SMD 代表已提交電信商並計費,第二段的 delivery_status 回 SS(即 DELIVRD)才是送達。
  • 第一段回 rc = -100 的五個失敗碼不計費,而且都是發送方可以事前避免的。
  • UNDELIV 多半是暫時性的,應隔一段時間重試一到兩次,不要當成永久失敗。
  • 收到 REJECTD 不要原樣重送,應告警給人看,改內容或查發送人設定。

做過 SMS 整合的人都遇過這個場景:API 回了 rc: 100,報表上顯示「成功」,但客戶說沒收到。背後的常見原因,詳見《SMS 沒有成功送達?揭開背後令人驚訝的原因》。

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

這篇把 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 綁定,全部在你的控制範圍內。Sender ID 的登記流程,詳見《香港 # Sender ID 申請全流程 — OFCA 短訊發送人登記制實務指南》。

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

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

電信商回報最終結果時才會有這一段。它的欄位和第一段不同:結果放在 delivery_status,SS 是已送達,也就是下表的 DELIVRD;SF 是未能送達,並多帶一個 desc_code,值就是下表其餘的狀態碼。

{ "ref_id": "145737475", "rc": 100, "rm": "Carrier has successfully delivered to the recipient's device", "delivery_status": "SS" }

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

三個最常被誤處理的狀態

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

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

UNKNWN 被算進失敗率。 這會讓你的送達率數字看起來比實際差。比較合理的做法是單獨統計它的比例 — 如果某條路由的 UNKNWN 比例明顯偏高,那本身就是一個訊號。路由品質為何會有差異,詳見《灰路 vs 直連電信商 — 為什麼便宜的 SMS 路由最後更貴》。

怎麼用這些資料對帳

用 ref_id 串起三個數字:

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

三個數字之間的落差,就是你真正該關心的東西。另外可以用 /v1/sms/get-usage-report 以日期區間取回 sms_count 與 points_used,和自己的紀錄互相驗證。想了解短訊怎樣計算段數,詳見《香港 SMS 計費怎麼算 — 字數、Unicode 與分段的完整解析》。

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

實作上的兩個提醒

Callback 要寫成冪等的。 用 ref_id 加狀態(第一段的 status、第二段的 delivery_status)作為唯一鍵。同一則 callback 有可能重複送達,如果你的處理邏輯會扣庫存或發郵件,重複執行就出事了。

Callback URL 要先在後台設定。 沒設定的話這兩段回報根本不會發出來 — 很多人以為是自己的 endpoint 有問題,其實是根本沒開。從建立 API key 到收到第一則 callback 的完整步驟,詳見《台灣簡訊 API 串接教學 — 從註冊到發出第一則 +886 簡訊(cURL/Node.js/Python/PHP 範例)》。

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