إعادة المحاولة واللاتكرارية
ما يُعاد، وما لا يُعاد عن قصد، ولماذا لا يمكن لإرسال مُعاد أن يتضاعف.
الإرسالات
يرفق العميل ترويسة Idempotency-Key بكل إرسال (emails.send وemails.sendBatch وtemplates.send)، تُولَّد مرة واحدة لكل **نداء** وتعيد محاولات ذلك النداء استخدامها. ويحجز API ذلك المفتاح قبل أن يرسل أي شيء، فتعيد المحاولة تشغيل الرسالة الأصلية بدل إرسال ثانية، بينما يظل نداءا send() المتعمّدان يرسلان مرتين. فهاتان نيتان مختلفتان وتبقيان مختلفتين.
مرّر idempotencyKey الخاص بك لمدّ ذلك الضمان عبر العمليات، فتُعيد مهمة انهارت ثم عملت من جديد تشغيل إرسالاتها بدل تكرارها.
await openemail.emails.send(message, { idempotencyKey: `invoice:${invoice.id}` })اشتقّه مما جعل الإرسال ضروريًا. ولا تشتقّه من ساعة أبدًا. وإعادة استخدام مفتاح بمتن مختلف تُرفض بـidempotency_key_reuse بدل أن تُعاد صامتةً.
كل ما عدا ذلك
تُعاد كل قراءة. أما الكتابة فلا تُعاد إلا حيث لا يمكن لطلب ثانٍ مطابق أن يعني شيئًا مختلفًا عن الأول، والإرسال مؤهل لذلك لأن مفتاح اللاتكرارية لديه يحوّل التكرار إلى إعادة تشغيل.
| النداء | يُعاد | لماذا |
|---|---|---|
| كل قراءة | نعم | لا شيء يتغير. |
| `emails.send`، `emails.sendBatch`، `templates.send` | نعم | مفتاح اللاتكرارية يجعل التكرار إعادة تشغيل. |
| `emails.cancel`، `emails.reschedule` | نعم | ضبط خالص لحالة مسمّاة. |
| `threads.update`، `threads.trash` | نعم | ضبط تسميات. وتطبيقه مرتين هو تطبيقه مرة واحدة. |
| `threads.snooze`، `threads.unsnooze` | نعم | لحظة الاستيقاظ موجودة في المتن، وليست مشتقة من وقت الوصول. |
| `labels.update`، `webhooks.update`، `settings.update`، `roles.update`، `members.update` | نعم | ضبط خالص لحقول مسمّاة. |
| `members.grantAddress`، `rules.reorder` | نعم | المنحة إدراج أو تحديث، والترتيب مذكور بالكامل. |
| `templates.publish` | نعم | نشر رأس منشور أصلًا يعيده دون تغيير. |
| `templates.preview`، `rules.test` | نعم | يعرضان أو يقيّمان، ولا يكتبان شيئًا. |
| `drafts.create`، `labels.create`، `webhooks.create`، `templates.create`، `rules.create`، `roles.create`، `tempMail.create` | لا | إعادة المحاولة تترك كائنين. |
| `drafts.update` | لا | اقرأ المعرّف من نتيجة كل كتابة بدل إعادة استخدام المعرّف الذي أرسلته. |
| `drafts.delete`، `labels.delete`، `webhooks.delete`، `templates.delete`، `rules.delete`، `roles.delete`، `members.remove`، `members.revokeAddress`، `tempMail.delete`، `tempMail.deleteMessage` | لا | إعادة المحاولة بعد استجابة ضائعة تبلّغ بإخفاق عن عمل قد نجح. |
| `webhooks.rotateSecret` | لا | التدوير الثاني يبطل السر الذي أعادته المحاولة الأولى. |
| `webhooks.test` | لا | سيرسل تسليمًا اصطناعيًا ثانيًا. |
| `emails.translate` | لا | ينفق نداءات نموذج، فإعادة المحاولة بعد طلب بلا جواب تشتري الجواب نفسه مرتين. |
| كل كتابة أخرى | لا | تُرسل مرة واحدة، ويُبلَّغ عن الإخفاق بدل تكراره. |
التراجع التدريجي
- محدودة بـ
maxRetriesعلى العميل، وافتراضها محاولتان إضافيتان. - لا تحدث إلا بعد إخفاق في الشبكة أو
408أو500أو502أو503أو504. ولا يُعاد429إلا حين يحمل ترويسةRetry-After، وهذا API لا يرسلها، فيرمي حدّ المعدل استثناءً فورًا. وأي رمز حالة آخر يرمي في الحال. - أسّية من نصف ثانية حتى ثماني ثوانٍ، مع تشويش زمني، كي لا يعيد أسطول من العملاء مزامنة نفسه عند التعافي.
- تُضبط وتيرتها بـ
Retry-Afterبأي من صيغتيها، delay-seconds وHTTP-date. وحين يحدّد الخادم مدة انتظار، ينتظر العميل تلك المدة بالضبط بدل التراجع التدريجي. - الخادم الذي يطلب أكثر من دقيقة يُعامَل كمن يقول للعميل توقّف لا نَمْ، فيُرفع الخطأ ومعه
retryAfterSeconds. فالعودة قبل ما طلب ليست احترامًا لطلبه. - لا يُعاد أبدًا طلب أُجهض بـ
AbortSignalمن المنادي. فالإجهاض يرميOpenEmailNetworkErrorفي الحال، من الطلب أو من الانتظار السابق للمحاولة التالية.