تخطَّ إلى المستندات
Ruby

إعادة المحاولة واللاتكرارية

ما يُعاد، وما لا يُعاد عن قصد، ولماذا لا يمكن لإرسال مُعاد أن يتضاعف.

الإرسالات

يرفق العميل ترويسة Idempotency-Key بكل إرسال (emails.send وemails.send_batch وtemplates.send وbroadcasts.send)، تُولَّد مرة واحدة لكل **استدعاء** على شكل oe- متبوعة بـ UUID عشوائي، وتعيد محاولات ذلك الاستدعاء استخدامها. ويحجز API ذلك المفتاح قبل أن يرسل أي شيء، فتعيد المحاولة تشغيل الرسالة الأصلية بدل إرسال رسالة ثانية، بينما يرسل استدعاءان مقصودان لـ send مرتين. فهاتان نيّتان مختلفتان وتبقيان مختلفتين.

مرّر idempotency_key: الخاص بك لمدّ ذلك الضمان عبر العمليات، فتُعيد مهمة انهارت ثم عملت من جديد تشغيل إرسالاتها بدل تكرارها. وتجيب إعادة التشغيل بـ replayed مضبوطًا على true وبالرسالة المخزَّنة كما هي الآن.

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]

اشتقّه مما جعل الإرسال ضروريًا. ولا تشتقّه من ساعة أبدًا. وإعادة استخدام مفتاح بمتن مختلف تُرفض بالخطأ 422 idempotency_key_reuse بدل أن تُعاد صامتةً. والمفتاح من 1 إلى 255 حرفًا من الحروف أو الأرقام أو _ أو . أو : أو -، وأي شيء آخر يعطي 400 invalid_idempotency_key.

كل ما عدا ذلك

كل GET تُعاد محاولته. أما الكتابة فلا تُعاد محاولتها إلا حيث لا يمكن لطلب ثانٍ مطابق أن يعني شيئًا مختلفًا عن الأول، والإرسال مؤهل لذلك لأن مفتاح اللاتكرارية الخاص به يحوّل التكرار إلى إعادة تشغيل.

الاستدعاءيُعادالسبب
كل GETنعملا شيء يتغير.
emails.send, emails.send_batch, templates.send, broadcasts.sendنعممفتاح اللاتكرارية يجعل التكرار إعادة تشغيل.
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نعمالمنح عملية إدراج أو تحديث، والترتيب يُذكر كاملًا.
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، وهذه الواجهة لا ترسله، فيرفع تجاوز حد المعدل خطأً فورًا. وأي حالة أخرى ترفع خطأً على الفور.
  • أسّية من نصف ثانية حتى ثماني ثوانٍ، مع تشويش زمني: كل انتظار نقطة عشوائية بين نصف ذلك الحد الأقصى وكامله، كي لا يعيد أسطول من العملاء مزامنة نفسه عند التعافي.
  • تُضبط وتيرتها بـRetry-After بأي من صيغتيها، delay-seconds وHTTP-date. وحين يحدّد الخادم مدة انتظار، ينتظر العميل تلك المدة بالضبط بدل التراجع التدريجي.
  • الخادم الذي يطلب أكثر من دقيقة يُعامَل كمن يقول للعميل توقّف لا انتظر، فيُرفع الخطأ ومعه retry_after_seconds. فالعودة قبل ما طلب ليست احترامًا لطلبه.
  • انتهاء المهلة فشل في الشبكة كأي فشل آخر، فالاستدعاء الذي يمكن تكراره بأمان يُجرَّب مجددًا بعده، وتنطبق timeout: على كل محاولة من جديد.
  • فترات الانتظار استدعاءات sleep داخل الاستدعاء، فينتظر خيط التنفيذ المستدعي أيضًا، ولا يعود الاستدعاء أو يرفع خطأً إلا بعد انتهاء محاولته الأخيرة.