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

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

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

الإرسالات

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

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

idempotency.php
$invoiceId = 'inv_2026_09_4192'; $sent = $client->emails->send([    'from' => '[email protected]',    'to' => '[email protected]',    'subject' => 'Your September invoice',    'text' => 'Invoice attached.',], idempotencyKey: 'invoice:' . $invoiceId); echo $sent['id'], ' ', ($sent['replayed'] ?? false) ? 'replayed' : 'sent', PHP_EOL;

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

كل ما عدا ذلك

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

الاستدعاءيُعادالسبب
كل GETنعملا شيء يتغير.
emails->send, emails->sendBatch, 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->updateAddress, domains->updateAddressForward, contacts->update, audiences->update, keys->update, forms->update, branding->update, threads->updateNote, chats->rename, account->setEmailNotification, account->setPushMuted, appHost->set, workspaces->setActiveنعمضبط خالص لحقول مسمّاة.
members->grantAddress, members->grantDomain, rules->reorder, threads->reorderNotesنعمالمنح عملية إدراج أو تحديث، والترتيب يُذكر كاملًا.
templates->publish, forms->publish, imports->startنعمنشر ما هو منشور أصلًا، أو بدء استيراد بدأ بالفعل، يعيده دون تغيير.
templates->preview، templates->render، broadcasts->preview، rules->testنعمتعرض أو تعدّ أو تقيّم، ولا تكتب شيئًا.
domains->verify, appHost->verify, senders->researchنعمالفحص المكرّر لا يغيّر شيئًا سوى وقت إجرائه.
contacts->save, contacts->setAudiences, contacts->removePhoto, contacts->block, contacts->unblock, contacts->deleteMany, keys->revoke, files->revokeLink, files->revokeAllLinks, appHost->delete, account->removePhoto, branding->removeImage, domains->removeLogo, domains->removeLogoCertificate, domains->removeAddressPhoto, account->acceptInvitation, account->declineInvitation, forms->approveSubmission, subscriptions->moveنعمكلٌّ منها يصرّح بالنتيجة النهائية، فيترك النداء الثاني ما تركه الأول.
audiences->addContact, audiences->addContacts, audiences->removeContacts, audiences->importContacts, suppressions->add, domains->createAddressنعميجد التكرار عمل النداء الأول منجَزًا فيبلّغ عنه بدل أن يؤديه مرتين.
contacts->setPhoto, account->setPhoto, branding->uploadImage, domains->setLogo, domains->setLogoCertificate, domains->setAddressPhoto, imports->uploadChunkنعمالبايتات المرسلة مجددًا تستبدل ما خزّنته المحاولة الأولى.
drafts->create, labels->create, webhooks->create, templates->create, rules->create, roles->create, tempMail->create, files->uploadلاإعادة المحاولة تترك كائنين.
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لاسيرسل تسليمًا اصطناعيًا ثانيًا.
webhooks->replayDeliveryلاسترسل الحدث إلى مستقبِلك مرة ثانية.
emails->translate, emails->compose, emails->rewrite, emails->suggestSubjectلاكل واحد منها يستهلك استدعاءات للنموذج، فإعادة المحاولة بعد طلب لم يُجَب تشتري الإجابة نفسها مرتين.
security->beginStepUp, security->verifyStepUpلاقد ترسل إعادة المحاولة بريدًا ثانيًا أو تستهلك محاولة ثانية للرمز.
كل استدعاء آخر ليس GETلاتُرسل مرة واحدة، ويُبلَّغ عن الإخفاق بدل تكراره.

التراجع التدريجي

  • محدودة بـ maxRetries: على العميل، والقيمة الافتراضية محاولتان إضافيتان. وmaxRetries: 0 توقف إعادة المحاولة.
  • لا تحدث إلا بعد إخفاق في الشبكة أو 408 أو 500 أو 502 أو 503 أو 504. ولا يُعاد 429 إلا حين يحمل ترويسة Retry-After، وهذا API لا يرسلها، فيرمي حدّ المعدل استثناءً فورًا. وأي رمز حالة آخر يرمي في الحال.
  • أسّية من نصف ثانية حتى ثماني ثوانٍ، مع تشويش زمني: كل انتظار نقطة عشوائية بين نصف ذلك الحد الأقصى وكامله، كي لا يعيد أسطول من العملاء مزامنة نفسه عند التعافي.
  • تُضبط وتيرتها بـRetry-After بأي من صيغتيها، delay-seconds وHTTP-date. وحين يحدّد الخادم مدة انتظار، ينتظر العميل تلك المدة بالضبط بدل التراجع التدريجي.
  • الخادم الذي يطلب أكثر من دقيقة يُعامَل كمن يقول للعميل توقّف لا انتظر، فيُرمى الاستثناء ومعه retryAfterSeconds. فالعودة قبل ما طلب ليست احترامًا لطلبه.
  • انتهاء المهلة فشل في الشبكة كأي فشل آخر، فالاستدعاء الذي يمكن تكراره بأمان يُجرَّب مجددًا بعده، وتنطبق timeout: على كل محاولة من جديد.
  • فترات الانتظار استدعاءات sleep وusleep داخل الاستدعاء، فينتظر السكربت أيضًا، ولا يعود الاستدعاء أو يرمي استثناءً إلا بعد انتهاء محاولته الأخيرة. وفي طلب ويب ينتظره شخص، اجعل timeout: وmaxRetries: منخفضين.