Zur Dokumentation springen
Ruby

Wiederholungsversuche und Idempotenz

Was wiederholt wird, was bewusst nicht, und warum ein wiederholter Sendevorgang nichts doppelt verschicken kann.

Sendevorgänge

Der Client hängt an jeden Sendevorgang (emails.send, emails.send_batch, templates.send und broadcasts.send) einen Idempotency-Key an, einmal pro **Aufruf** als oe- mit einer zufälligen UUID erzeugt und von den Wiederholungen dieses Aufrufs weiterverwendet. Die API belegt diesen Key, bevor sie irgendetwas ausliefert. Eine Wiederholung spielt daher die ursprüngliche Nachricht erneut ab, statt eine zweite zu senden, während zwei bewusste send-Aufrufe weiterhin zweimal senden. Das sind unterschiedliche Absichten, und sie bleiben unterschiedlich.

Übergeben Sie einen eigenen idempotency_key:, um diese Garantie über Prozessgrenzen hinweg auszudehnen, sodass ein Job, der abgestürzt und erneut gelaufen ist, seine Sendevorgänge erneut abspielt, statt sie zu wiederholen. Ein erneutes Abspielen antwortet mit replayed gleich true und der gespeicherten Nachricht in ihrem aktuellen Zustand.

idempotency.rb
invoice_id = "inv_2026_09_4192" sent = client.emails.send(  from: "[email protected]",  to: "[email protected]",  subject: "Your September invoice",  text: "Invoice attached.",  idempotency_key: "invoice:#{invoice_id}") puts sent[:id], sent[:replayed]

Leiten Sie ihn aus dem ab, was den Sendevorgang nötig gemacht hat. Nie aus einer Uhr. Ein Key, der mit einem anderen Body wiederverwendet wird, wird mit einem 422 idempotency_key_reuse abgelehnt, statt stillschweigend erneut abgespielt zu werden. Ein Key besteht aus 1 bis 255 Zeichen aus Buchstaben, Ziffern, _, ., : oder -, alles andere ergibt einen 400 invalid_idempotency_key.

Alles Übrige

Jedes GET wird wiederholt. Ein Schreibvorgang wird nur dort wiederholt, wo ein zweiter identischer Request nichts anderes bedeuten kann als der erste, und ein Sendevorgang erfüllt das, weil sein Idempotency-Key aus einer Wiederholung ein erneutes Abspielen macht.

AufrufWiederholtWarum
Jedes GETJaEs ändert sich nichts.
emails.send, emails.send_batch, templates.send, broadcasts.sendJaEin Idempotency-Key macht aus einer Wiederholung ein erneutes Abspielen.
emails.cancel, emails.reschedule, broadcasts.cancel, forms.pause, forms.resume, threads.restoreJaEin reines Setzen eines benannten Zustands.
threads.update, threads.trashJaEin Setzen von Labels. Zweimal anwenden ist dasselbe wie einmal anwenden.
threads.snooze, threads.unsnoozeJaDer Weckzeitpunkt steht im Body und wird nicht aus dem Eingangszeitpunkt abgeleitet.
emails.update, labels.update, webhooks.update, settings.update, roles.update, members.update, domains.update, domains.update_address, domains.update_address_forward, contacts.update, audiences.update, keys.update, forms.update, branding.update, threads.update_note, chats.rename, account.set_email_notification, account.set_push_muted, app_host.set, workspaces.set_activeJaEin reines Setzen benannter Felder.
members.grant_address, members.grant_domain, rules.reorder, threads.reorder_notesJaEine Vergabe ist ein Upsert, und eine Reihenfolge wird vollständig angegeben.
templates.publish, forms.publish, imports.startJaDas Veröffentlichen von bereits Veröffentlichtem oder das Starten eines bereits gestarteten Imports gibt es unverändert zurück.
templates.preview, templates.render, broadcasts.preview, rules.testJaSie rendern, zählen oder werten aus und schreiben nichts.
domains.verify, app_host.verify, senders.researchJaEine wiederholte Prüfung ändert nichts außer dem Zeitpunkt, zu dem sie stattfand.
contacts.save, contacts.set_audiences, contacts.remove_photo, contacts.block, contacts.unblock, contacts.delete_many, keys.revoke, files.revoke_link, files.revoke_all_links, app_host.delete, account.remove_photo, branding.remove_image, domains.remove_logo, domains.remove_logo_certificate, domains.remove_address_photo, account.accept_invitation, account.decline_invitation, forms.approve_submission, subscriptions.moveJaJeder gibt das Endergebnis an, ein zweiter Aufruf hinterlässt also, was der erste hinterlassen hat.
audiences.add_contact, audiences.add_contacts, audiences.remove_contacts, audiences.import_contacts, suppressions.add, domains.create_addressJaEine Wiederholung findet die Arbeit des ersten Aufrufs bereits erledigt vor und meldet sie, statt sie zweimal zu tun.
contacts.set_photo, account.set_photo, branding.upload_image, domains.set_logo, domains.set_logo_certificate, domains.set_address_photo, imports.upload_chunkJaErneut gesendete Bytes ersetzen, was der erste Versuch gespeichert hat.
drafts.create, labels.create, webhooks.create, templates.create, rules.create, roles.create, temp_mail.create, files.uploadNeinEin Wiederholungsversuch hinterlässt zwei Objekte.
drafts.updateNeinLesen Sie die ID aus dem Ergebnis jedes Schreibvorgangs, statt die gesendete erneut zu verwenden.
drafts.delete, labels.delete, webhooks.delete, templates.delete, rules.delete, roles.delete, members.remove, members.revoke_address, temp_mail.delete, temp_mail.delete_messageNeinEin Wiederholungsversuch nach einer verlorenen Antwort meldet einen Fehlschlag für Arbeit, die erfolgreich war.
webhooks.rotate_secretNeinEine zweite Rotation macht das Secret ungültig, das der erste Versuch zurückgegeben hat.
webhooks.testNeinSie würde eine zweite synthetische Zustellung senden.
webhooks.replay_deliveryNeinEs würde das Event ein zweites Mal an Ihren Empfänger senden.
emails.translate, emails.compose, emails.rewrite, emails.suggest_subjectNeinJeder verbraucht Modellaufrufe, daher bezahlt ein Wiederholungsversuch nach einer unbeantworteten Anfrage dieselbe Antwort zweimal.
security.begin_step_up, security.verify_step_upNeinEin Wiederholungsversuch könnte eine zweite E-Mail senden oder einen zweiten Versuch für den Code verbrauchen.
Jeder andere Aufruf, der kein GET istNeinEinmal gesendet; ein Fehlschlag wird gemeldet statt wiederholt.

Der Backoff

  • Begrenzt durch max_retries: am Client, standardmäßig zwei zusätzliche Versuche. max_retries: 0 schaltet Wiederholungsversuche ab.
  • Nur nach einem Netzwerkfehler oder einem 408, 500, 502, 503 oder 504. Ein 429 wird nur wiederholt, wenn er ein Retry-After mitführt, und diese API sendet keines, sodass ein Rate Limit sofort einen Fehler auslöst. Jeder andere Status löst umgehend einen Fehler aus.
  • Exponentiell von einer halben Sekunde bis zu acht, mit Jitter: Jede Wartezeit ist ein zufälliger Punkt zwischen der Hälfte dieser Obergrenze und der ganzen, damit sich eine Flotte bei der Wiederherstellung nicht neu synchronisiert.
  • Getaktet durch Retry-After in beiden Formen, delay-seconds und HTTP-date. Nennt der Server eine Wartezeit, wartet der Client genau so lange, statt einen Backoff anzuwenden.
  • Fordert ein Server mehr als eine Minute, wird das als Aufforderung an den Client verstanden, aufzuhören, und nicht, zu schlafen. Der Fehler wird daher mit retry_after_seconds ausgelöst. Früher zurückzukommen als gefordert hieße, die Vorgabe nicht zu beachten.
  • Ein Timeout ist ein Netzwerkfehler wie jeder andere. Ein Aufruf, der gefahrlos wiederholt werden kann, wird danach also erneut versucht, und timeout: gilt für jeden Versuch neu.
  • Die Wartezeiten sind sleep-Aufrufe innerhalb des Aufrufs. Der aufrufende Thread wartet also mit, und der Aufruf kehrt erst zurück oder löst einen Fehler aus, wenn sein letzter Versuch vorbei ist.