تلاش مجدد و idempotency
چه چیزی دوباره تلاش میشود، چه چیزی عمداً نه، و چرا یک ارسالِ دوبارهتلاششده نمیتواند تکراری شود.
ارسالها
کلاینت به هر ارسال (emails.send، emails.send_batch، templates.send و broadcasts.send) یک Idempotency-Key میچسباند که بهازای هر **فراخوانی** یک بار به شکل oe- و یک UUID تصادفی تولید میشود و همان فراخوانی در تلاشهای دوبارهاش از آن استفاده میکند. API پیش از آنکه چیزی را روانه کند آن کلید را ثبت میکند، پس یک تلاش دوباره همان پیام اصلی را بازپخش میکند نه اینکه پیام دومی بفرستد، در حالی که دو فراخوانی عمدی send همچنان دو بار میفرستند. اینها دو نیت متفاوتاند و متفاوت میمانند.
با دادن idempotency_key: خودتان، این تضمین را در میان فرایندها گسترش دهید، تا کاری که از کار افتاده و دوباره اجرا شده است ارسالهایش را بازپخش کند نه اینکه تکرارشان کند. بازپخش با replayed برابر true و پیام ذخیرهشده در وضعیت کنونیاش پاسخ میدهد.
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]آن را از همان چیزی مشتق کنید که ارسال را لازم کرده است. هرگز از ساعت. استفادهٔ دوباره از یک کلید با بدنهای متفاوت با یک 422 idempotency_key_reuse رد میشود، نه اینکه بیصدا بازپخش شود. کلید 1 تا 255 نویسه از حروف، ارقام، _، .، : یا - است، و هر چیز دیگری یک 400 invalid_idempotency_key است.
باقی موارد
هر GET دوباره تلاش میشود. یک نوشتن تنها جایی دوباره تلاش میشود که درخواست دومِ همسان نتواند معنایی متفاوت از اولی داشته باشد، و ارسال واجد شرایط است چون کلید idempotency آن یک تکرار را به بازپخش تبدیل میکند.
| فراخوانی | تلاش مجدد | چرا |
|---|---|---|
| هر GET | بله | چیزی تغییر نمیکند. |
| emails.send, emails.send_batch, templates.send, broadcasts.send | بله | یک کلید idempotency، تکرار را به بازپخش تبدیل میکند. |
| emails.cancel, emails.reschedule, broadcasts.cancel, forms.pause, forms.resume, threads.restore | بله | یک تنظیم خالصِ حالتی نامبرده. |
| threads.update, threads.trash | بله | تنظیم برچسب است. دو بار اعمال کردنش همان یک بار اعمال کردن است. |
| threads.snooze, threads.unsnooze | بله | لحظهٔ بیدارباش در بدنه است، نه مشتقشده از زمان رسیدن. |
| 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_active | بله | یک تنظیم خالصِ فیلدهای نامبرده. |
| members.grant_address, members.grant_domain, rules.reorder, threads.reorder_notes | بله | اعطا یک upsert است، و ترتیب بهطور کامل بیان میشود. |
| templates.publish, forms.publish, imports.start | بله | انتشار چیزی که از پیش منتشر شده است، یا آغاز ایمپورتی که از پیش آغاز شده است، همان را بدون تغییر برمیگرداند. |
| templates.preview, templates.render, broadcasts.preview, rules.test | بله | رندر میکنند، میشمارند یا ارزیابی میکنند و چیزی نمینویسند. |
| domains.verify, app_host.verify, senders.research | بله | بررسی تکراری چیزی جز زمان انجامش را تغییر نمیدهد. |
| 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.move | بله | هرکدام نتیجهٔ نهایی را بیان میکند، پس فراخوانی دوم همان چیزی را به جا میگذارد که اولی به جا گذاشت. |
| audiences.add_contact, audiences.add_contacts, audiences.remove_contacts, audiences.import_contacts, suppressions.add, domains.create_address | بله | تکرار، کار فراخوانی اول را انجامشده مییابد و بهجای دو بار انجام دادنش آن را گزارش میکند. |
| contacts.set_photo, account.set_photo, branding.upload_image, domains.set_logo, domains.set_logo_certificate, domains.set_address_photo, imports.upload_chunk | بله | بایتهایی که دوباره فرستاده میشوند جایگزین چیزی میشوند که تلاش اول ذخیره کرده بود. |
| drafts.create, labels.create, webhooks.create, templates.create, rules.create, roles.create, temp_mail.create, files.upload | خیر | تلاش مجدد دو شیء به جا میگذارد. |
| drafts.update | خیر | بهجای استفادهٔ دوباره از شناسهای که فرستادهاید، شناسه را از نتیجهٔ هر نوشتن بخوانید. |
| drafts.delete, labels.delete, webhooks.delete, templates.delete, rules.delete, roles.delete, members.remove, members.revoke_address, temp_mail.delete, temp_mail.delete_message | خیر | تلاش مجدد پس از گم شدن یک پاسخ، برای کاری که موفق شده است شکست گزارش میکند. |
| webhooks.rotate_secret | خیر | چرخش دوم، کلید مخفیای را که تلاش اول برگردانده بود باطل میکند. |
| webhooks.test | خیر | یک تحویل ساختگی دوم میفرستاد. |
| webhooks.replay_delivery | خیر | رویداد را بار دوم به گیرندهٔ شما میفرستاد. |
| emails.translate, emails.compose, emails.rewrite, emails.suggest_subject | خیر | هرکدام فراخوانی مدل خرج میکند، پس تلاش دوباره پس از درخواستی بیپاسخ، همان پاسخ را دو بار میخرد. |
| security.begin_step_up, security.verify_step_up | خیر | تلاش دوباره ممکن است ایمیل دومی بفرستد یا تلاش دومی را روی کد مصرف کند. |
| هر فراخوانی دیگری که GET نیست | خیر | یک بار فرستاده میشود، و شکست گزارش میشود نه تکرار. |
بکآف
- محدود به
max_retries:روی کلاینت، که پیشفرضش دو تلاش اضافی است.max_retries: 0تلاشهای دوباره را خاموش میکند. - تنها پس از یک شکست شبکه یا یکی از
408،500،502،503یا504. یک429فقط وقتی دوباره تلاش میشود کهRetry-Afterداشته باشد، و این API چنین چیزی نمیفرستد، پس محدودیت نرخ بیدرنگ raise میشود. هر وضعیت دیگری فوراً raise میشود. - نمایی از نیم ثانیه تا هشت ثانیه، با jitter: هر انتظار نقطهای تصادفی میان نیمی از آن سقف و کل آن است، تا یک ناوگان هنگام بازیابی دوباره همزمان نشود.
- با
Retry-Afterدر هر دو شکلش تنظیم میشود، delay-seconds و HTTP-date. وقتی سرور مدت انتظاری را نام ببرد، کلاینت دقیقاً همانقدر صبر میکند بهجای عقبنشینی نمایی. - سروری که بیش از یک دقیقه بخواهد، چنین تفسیر میشود که به کلاینت میگوید بایست، نه اینکه بخواب، پس خطا با
retry_after_secondsروی آن raise میشود. زودتر از آنچه خواسته بازگشتن، احترام گذاشتن به آن نیست. - پایان مهلت هم یک شکست شبکه است مانند هر شکست دیگر، پس فراخوانیای که تکرارش بیخطر است پس از آن دوباره امتحان میشود، و
timeout:برای هر تلاش از نو اعمال میشود. - انتظارها فراخوانیهای
sleepدرون همان فراخوانی هستند، پس ترد فراخواننده هم منتظر میماند، و فراخوانی تنها وقتی آخرین تلاشش تمام شد برمیگردد یا خطا raise میکند.