Skip to main content

Zustellungslebenszyklus

Jede ausgehende Nachricht, die Sie über Orbit senden, trägt ein status-Feld, das sich weiterbewegt, während die Nachricht von Ihrem API-Aufruf zum Empfängergerät wandert — oder in einen Fehlerzustand endet. Diese Seite erklärt diesen Zustandsautomaten auf Konzeptebene: was die Zustände bedeuten, was jede Transition auslöst und wo die carrierspezifischen Besonderheiten liegen. Lesen Sie sie, bevor Sie Ihren ersten Webhook abonnieren oder Ihre Integration auf Nachrichtenergebnisse verzweigen. Die Semantik pro Status, die vollständige Transitionstabelle und die Webhook-Ereigniszuordnung stehen in der Referenz zum Nachrichtenstatus-Lebenszyklus; das Antwortschema zum Lesen des aktuellen Zustands einer Nachricht in der Messaging-API-Referenz. Diese Seite verbindet beides auf höherer Ebene.

Der Erfolgspfad

Eine Nachricht, die durchgehend erfolgreich ist, durchläuft: pending → queued → sending → sent → delivered → read Jede Transition wird von einem anderen Akteur ausgelöst — keine einzelne Partei sieht den gesamten Ablauf: Zwei Akteure stehen neben diesem Pfad und können einen Eintrag aus ihm herausnehmen:
  • Der No-DLR-Scheduler. Wenn ein Carrier nie eine Zustellbestätigung zurückliefert, hebt ein Scheduler den Zustand sent nach einem kanalspezifischen Zeitfenster auf submitted_no_receipt — siehe Carrier-bestätigt vs. Zwischenzustand auf der Leitung.
  • Sie (der Betreiber). Ein Abbruch einer noch nicht gesendeten geplanten Nachricht setzt sie auf cancelled, einen Endzustand, den kein Provider-Aufruf und keine DLR je berührt. Sandbox-Sendungen lösen in test_sent auf, einen Endzustand, der vor jeder Provider-Übergabe erreicht wird. Das Löschen eines Eintrags setzt jeden Endzustand auf deleted, ab dem nichts mehr ihn ändern kann.
Eine für die Zukunft geplante Nachricht ruht in scheduled bis zu ihrem Auslösezeitpunkt und tritt dann der Warteschlange bei. Sie kann scheduled nur auf drei Arten verlassen: Hochstufung zu queued zur Auslösezeit, cancelled durch Sie oder expired, wenn ihr Gültigkeitsfenster vor dem Versand abläuft.

Carrier-bestätigt vs. Zwischenzustand auf der Leitung

Die für Reporting und Abgleich wichtigste Unterscheidung ist, ob ein Zustand ein vom Carrier bestätigtes Ergebnis oder ein Zwischenzustand auf der Leitung ist:
  • delivered und read sind carrier-bestätigt. Eine echte DLR ist eingegangen; der Carrier selbst hat das Ergebnis behauptet.
  • submitted_no_receipt ist ein Zwischenzustand auf der Leitung. Er bedeutet „die Einreichung wurde angenommen, und innerhalb des Zeitfensters kam keine Bestätigung zurück”. Er wird auf jedem Webhook als Zwischenzustand markiert (state_class: "intermediate", is_terminal: false), weil eine echte delivered-, read- oder Fehler-DLR noch nachträglich eintreffen und ihn überschreiben kann.
Behandeln Sie submitted_no_receipt als Ergebnis unbekannt, nicht als Zustellung. Ob „unbekannt” eher positiv oder wirklich zweideutig ist, hängt vom Kanal ab — siehe Kanalabhängige Besonderheiten. expired ist das Gegenstück auf der anderen Seite: Eine DLR ist eingegangen, aber so spät, dass das Bestätigungsfenster bereits geschlossen war. Das Ergebnis ist nicht bestimmbar, und der Eintrag ist abgeschlossen; expired wird den Abonnenten als message.failed-Ereignis gemeldet.

Kanalabhängige Besonderheiten

  • Meta-DM-Kanäle (Instagram, Messenger). Metas Send-API gibt nie Zustellbestätigungen aus. Der Versand verlässt Orbit als sent, wird in den Nachrichtenmetadaten als no_dlr_channel markiert und kippt nach 5 Minuten auf submitted_no_receipt. Für einen opt-imierten Empfänger garantiert Meta die Zustellung bei Annahme — auf diesen Kanälen verhält sich submitted_no_receipt also wie ein funktionales Zustellsignal, und die Nachrichtenmetadaten tragen no_dlr_channel: true, damit Sie diesen Fall erkennen.
  • SMPP-gestützte Kanäle (SMS, MMS, Sprache, Fax, RCS). Das Zeitfenster beträgt 30 Minuten. Hier ist submitted_no_receipt wirklich zweideutig: Das Gerät kann die Nachricht empfangen haben, ohne dass eine Bestätigung gemeldet wurde, der Carrier sendet auf dieser Route möglicherweise nie Bestätigungen, oder die Bestätigung wurde unterwegs verworfen. Verfolgen Sie diese Rate getrennt von Ihrer Zustellrate — ein steigender submitted_no_receipt-Anteil auf einem Ziel deutet auf eine nicht kooperierende Route oder einen defekten Bestätigungspfad hin und ist in jedem Fall untersuchenswert.
  • E-Mail. Fügt ein Fehlerergebnis hinzu, das anderen Kanälen fehlt: bounced, wenn der empfangende Mailserver die Nachricht abweist. Bounced zählt wie failed zu Ihrer Endfehlerrate, ist aber ein eigener Status, damit Sie empfängerseitige von providerseitigen Ablehnungen trennen können.
  • Betreiberabbruch. cancelled ist nur von Ihnen erreichbar — über POST /messages/:id/cancel auf einer unversendeten Nachricht. Kein Carrier schreibt ihn je, und dementsprechend löst er kein Webhook-Ereignis aus; nichts benachrichtigt einen Abonnenten von einem Abbruch. Fragen Sie GET /messages/:id ab, wenn Sie den Abbruch in Ihrer eigenen Oberfläche bereitstellen und beobachten müssen.
  • Sandbox / Testmodus. Testsendungen lösen in test_sent auf, bevor irgendeine Provider-Übergabe stattfindet. Sie lösen message.sent mit status: "test_sent" und metadata.test_mode: true aus, sodass ein Abonnent auf metadata.test_mode verzweigen muss, um Sandbox-Verkehr aus der Produktionsverarbeitung fernzuhalten.

Auf was zu verzweigen ist

Integrationen sollten nur auf maschinenlesbaren Feldern umschalten, nie auf Anzeigenamen:
  • status — der aktuelle Zustand der Nachricht. Dies ist der primäre Verzweigungspunkt. Behandeln Sie die vollständige Menge: neben den Erfolgs- und üblichen Fehlerzuständen vergessen Sie nicht cancelled, test_sent, submitted_no_receipt und bounced.
  • metadata.classified_error_code — bei Endfehlern vorhanden; die normalisierte, maschinenlesbare Fehlerkategorie. Kombinieren Sie sie mit dem rohen error_code / error_message auf message.failed-Webhooks, wenn Sie den Carrier-eigenen Wortlaut haben möchten.
  • metadata.no_dlr_channel — auf Meta-DM-Einträgen vorhanden; besagt, dass ein submitted_no_receipt der zustellgarantierte Meta-Fall ist, nicht der zweideutige SMPP-Fall.
  • state_class / is_terminal — auf Lebenszyklus-Webhooks. is_terminal: true (äquivalent state_class: "terminal") bedeutet, dass das Ergebnis endgültig ist; submitted_no_receipt meldet is_terminal: false nur deshalb, damit Sie den Vorgang nicht endgültig abschließen.

Häufige Fallstricke

  1. message.created als Annahme behandeln. message.created feuert in dem Moment, in dem der Eintrags der Warteschlange beitritt, vor jedem Provider-Aufruf. Eine synchrone Ablehnung (ein 4xx beim Versand oder ein sofortiges rejected/failed) lässt dieses Ereignis trotzdem ausgeliefert. Kombinieren Sie es mit message.sent, bevor Sie schließen, dass die Nachricht rausging.
  2. Annehmen, delivered sei unveränderbar. Carrier auf manchen Routen geben eine Zustellbestätigung aus und dann Minuten später eine Korrektur — manche indische und brasilianische Carrier tun dies. Orbit folgt dem: Der Eintrag kann von delivered → undelivered oder delivered → failed wandern. Wenn Sie Status in Ihren eigenen Datenspeicher spiegeln, wenden Sie Aktualisierungen idempotent über die Nachrichten-ID an, statt Transitionen für eine Nachricht zu ignorieren, die Sie bereits als zugestellt markiert haben.
  3. Auf einen Webhook zu cancelled warten. Er wird nie kommen — der Abbruch stammt von Ihrem eigenen API-Aufruf, nicht von einem Carrier-Rückruf, sodass die Plattform dafür kein Ereignis ausgibt. Der Eintrag ruht schlicht auf cancelled, bis Sie ihn löschen.
  4. submitted_no_receipt in den Fehler-Eimer werfen. Es trifft zu Transportgründen auf dem Ereignistyp message.failed ein (es gibt kein eigenes Ereignis), aber sein status in der Nutzlast ist submitted_no_receipt mit is_terminal: false. Verzweigen Sie auf data.status, nicht auf den Ereignistyp, und halten Sie es aus Ihren Hard-Failure-Metriken heraus.
  5. Erwarten, genau ein Endereignis pro Nachricht. Eine Nachricht kann message.failed mit status: "submitted_no_receipt" und später message.delivered ausgeben, wenn eine langsame Carrier-Bestätigung innerhalb des Nachfolgefensters endlich eintrifft. Deduplizieren und rekoncizieren Sie nach message_id und lassen Sie das spätere Ereignis gewinnen.
Sobald der Zustandsautomat klar ist, finden Sie die Nutzlasten je Status in der Webhook-Ereignisreferenz und die Statussemantik auf Endpunktsebene in der Referenz zum Nachrichtenstatus-Lebenszyklus.