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

إرسال دفعة

`emails.send_batch`: حتى 100 رسالة، ونتائج لكل عنصر.

emails.send_batch

send_batch.rb
invoices = [  {number: "INV-1042", email: "[email protected]"},  {number: "INV-1043", email: "[email protected]"}] messages = invoices.map do |invoice|  {from: "[email protected]", to: invoice[:email], subject: "Invoice #{invoice[:number]}", text: "Your invoice is attached."}end result = client.emails.send_batch(messages, idempotency_key: "invoices:2026-09") puts "#{result.sent} sent, #{result.failed} failed" result.items.each do |item|  if item[:status] == "error"    warn "#{item[:index]} #{item.dig(:error, :code)} #{item.dig(:error, :message)}"  else    puts "#{item[:index]} #{item.dig(:email, :id)}"  endend

يأخذ send_batch مصفوفة Array من رسائل Hash، كل منها بشكل متن emails.send تمامًا، ويعيد OpenEmail::BatchResult. ويحمل items الخاص به Hash واحدًا لكل رسالة، بالترتيب، وكل منها إما ok مع رسالته وإما error مع الغلاف الذي كانت تلك الرسالة ستُرفض به. ولا يُتراجع عن شيء، فعدد failed فوق 0 قائمة يجب التصرف بشأنها لا سبب لإعادة إرسال الدفعة.

مفتاح واحد للاتكرارية يغطي الدفعة كلها ويوسّعه الخادم لكل عنصر، فالدفعة التي تُعاد محاولتها تعيد تشغيل كل رسالة بدل أن تطويها كلها على الرسالة الأولى. أرسل الـ Array نفسها بالترتيب نفسه حين تعيد المحاولة: فالعنصر الذي غيّر موضعه يرتبط بمفتاح موضع آخر ويعود بخطأ idempotency_key_reuse.

الرسالة المرفوضة لا ترفع خطأً. وحدها المشكلة في الدفعة ككل ترفع خطأً: Array فارغة، أو أكثر من 100 رسالة، أو أكثر من 10 تحمل translate، أو إخفاق في المفتاح أو النطاق، أو عطل في الخادم. والعطل في الخادم في منتصف الطريق يأتي بعد أن تكون العناصر الأولى قد ذهبت، فيعيد العميل المحاولة بالمفتاح نفسه، مما يعيد تشغيل تلك العناصر بدل إرسالها مرتين.

تُرسل العناصر واحدًا تلو الآخر داخل طلب واحد، فالدفعة الكبيرة من الإرسالات الفورية تستغرق وقتًا أطول بوضوح من send واحد. اجعل timeout: الخاص بالعميل سخيًا.

المعاملات: emails.send_batch

emailsArray<Hash>مطلوب
من رسالة واحدة إلى 100، تُرسل بالشكل `{"emails": [...]}` وتُقبل واحدة تلو الأخرى بالترتيب المعطى. وتمر كل منها بالمعالجة نفسها التي يجريها `emails.send`، فيُغلَّف المستلم المنفرد، ويصبح Time لحظة زمنية، وتُرمَّز بايتات المرفقات. ومصفوفة Array فارغة، أو أكثر من 100، أو أكثر من 10 رسائل تحمل `translate`، ترفض الاستدعاء كله بـ `validation_error` على `emails`. وكذلك يرفض الاستدعاءَ كلَّه غيابُ النطاق `emails:send` و`idempotency_key:` المشوّه، قبل إرسال رسالة واحدة.
idempotency_keyString
يمنع تكرار الدفعة عبر العمليات المختلفة. ويرفق العميل مفتاحًا مولَّدًا حديثًا مع كل استدعاء على أي حال، فإعادات محاولته لا تُرسل مرتين أبدًا، ويوسّع الخادم أي مفتاح يصله لكل عنصر بالشكل `key/0` و`key/1` وهكذا، مفصولًا بشرطة مائلة، وهي محرف لا يجوز أن يحتويه مفتاحك، فلا يمكن لمفتاح واحد على مئة رسالة أن يطويها على الأولى.
api_keyString
يرسل الدفعة بهذا المفتاح بدل مفتاح العميل.

كل رسالة في emails

fromString or Hashمطلوب
المُرسِل، في صورة عنوان مجرّد أو `Name <addr@host>` أو Hash فيه `email` و`name`. لا يوجد مُرسِل احتياطي، ويجب أن يكون هذا العنوان مسموحًا للمفتاح. والرفض يُفشل ذلك العنصر وحده، في صورة `permission_error` بالرمز `from_address_forbidden`.
toString, Hash or Arrayمطلوب
مستلم واحد على الأقل، والمستلم المفرد يلفّه العميل داخل Array. وبحد أقصى 50 عنوانًا عبر `to` و`cc` و`bcc` مجتمعة، تُحسب لكل رسالة لا عبر الدفعة.
ccString, Hash or Array
الافتراضي لا أحد، وتُحسب ضمن إجمالي الخمسين عنوانًا نفسه مع `to` و`bcc`.
bccString, Hash or Array
الافتراضي لا أحد، وتُحسب ضمن إجمالي الخمسين عنوانًا نفسه. و`Bcc` من الأسماء التي لا يجوز لـ `headers` ضبطها، فهذه هي الطريقة الوحيدة للنسخ المخفي. إذ إن صيغة الترويسة كانت ستنقض المُغلَّف المنفصل لكل مستلم وهو ما يبقي العنوان مخفيًا.
replyToString or Hash
إلى أين تذهب الردود. يُطبَّق بعد `headers`، فيطمس `Reply-To` الذي ضبطته هناك أيضًا بدلًا من إضافة ترويسة ثانية.
subjectString
بحد أقصى 998 حرفًا، وهو حد السطر في RFC 5322، والافتراضي String فارغ. والموضوع الفارغ يُستعاض عنه بموضوع القالب نفسه حين يوفّر `template` موضوعًا.
htmlString
جزء HTML، بحد أقصى مليون حرف، وهو الجزء الذي يراه المستلمون عندما يُعطى الجسمان معًا. ويلزم واحد من `html` أو `text` أو `template` أو `draftId`، والعنصر الخالي منها جميعًا يفشل بـ `validation_error` على `html`.
textString
جزء النص العادي، بحد أقصى مليون حرف. يمكن إرسال الاثنين، وكل وسيلة نقل في هذا المسار تبني متنًا واحدًا من String واحد، فيغلب `html` حيث يوجد.
headersHash
`X-*` و`List-*` وReply-To وPrecedence وAuto-Submitted وImportance وPriority وFeedback-ID فقط. وأي ترويسة يضبطها النقل بنفسه (From وTo وBcc وSubject وMessage-ID وترويسات DKIM وARC) تُرفض بوصفها `reserved_header` بدل أن تُحذف بصمت. والقيم بحد أقصى 998 حرفًا ولا يجوز أن تحمل CR أو LF أو NUL، لأن السطر الثاني ترويسة ثانية.
attachmentsArray<Hash>
بحد أقصى 20 ملفًا لكل رسالة، مع حد 5 ميغابايت للملفات المضمّنة مجتمعة بعد فك الترميز، يُحسب لكل رسالة لا لكل دفعة. ويكون `content` بترميز base64 عبر الشبكة. مرّر البايتات في صورة String ثنائي أو IO أو Pathname ويرمّزها العميل. وHash فيه `fileId` وحده يسمّي ملفًا موجودًا بالفعل في مساحة العمل ولا يُحتسب ضمن حد الملفات المضمّنة.
threadIdString
الرد داخل محادثة قائمة، بحد أقصى 256 حرفًا. ويكتب النقل منه ترويستي In-Reply-To وReferences، وهو ما يجعل الرد يحط داخل المحادثة لا بجانبها.
draftIdString
أرسل محتوى مسودة محفوظة تحت هذا المُغلَّف، بحد أقصى 256 حرفًا. والمستلمون والموضوع والترويسات المبنية هنا هي ما يخرج عبر الشبكة.
templateHash
اعرض قالبًا مخزَّنًا على الخادم، بالمعرّف (`tpl_…`) أو بالـ slug، مع تثبيت `version` لمراجعة بعينها وملء `props` و`slots` له. يُحسم مرة واحدة، عند قبول العنصر، ويُرفض مع `html` أو `text` ومع `draftId`، لأن كلًّا منها جواب ثانٍ عن محتوى الرسالة.
scheduledAtTime, DateTime or String
Time أو DateTime، أو لحظة ISO 8601، أو مدة مثل `PT1H`، بعد ثانية واحدة على الأقل في المستقبل و365 يومًا على الأكثر. وDate في Ruby يعني منتصف الليل UTC في ذلك اليوم. وتُجدوَل العناصر باستقلال، فيمكن أن تضم دفعة واحدة مئة موعد إرسال مختلف.
cancellableForSecondsInteger
نافذة تراجع بالثواني على إرسال فوري، من 0 إلى 900، والافتراضي 0. وأي قيمة فوق 0 تُرفض مع `scheduledAt` على العنصر نفسه، لأن الرسالة المجدولة قابلة للإلغاء أصلًا حتى تنطلق.
trackingHash
`opens` و`clicks`، كل منهما اختياري ويتجاوز الإعداد لهذه الرسالة وحدها. والمفتاح الذي تتركه يتبع العنوان الذي تُرسل منه الرسالة (أو الالتقاط الشامل الذي التقطها)، وهو معطَّل ما لم يفعّله ذلك العنوان.
tagsHash
بحد أقصى 10 وسوم، بمفاتيح من 1 إلى 64 حرفًا من المجموعة `A-Za-z0-9_-` وقيم حتى 256 حرفًا. تُعاد كما هي على الرسالة ولا تُفسَّر أبدًا: يرشّح `emails.list` حسب `status:` و`from:` و`broadcast_id:` ونافذة الجدولة لا غير، فالوسم شيء تقرؤه من رسالة تملكها بالفعل لا وسيلة للعثور عليها.
translateHash
أرسل هذا العنصر بلغة أخرى، ويُحسم ذلك عند القبول فتكون الكلمات التي اعتُمدت هي الكلمات التي تخرج. ولا يجوز أن يحمله أكثر من 10 عناصر في دفعة واحدة: فكل عنصر ينفق عدة استدعاءات للنموذج والعناصر تُنفَّذ بالترتيب، فدفعة أكبر كانت ستُقطع في منتصف الإرسال. وفوق ذلك يُرفض الاستدعاء كله بـ `too_many_items` على `emails`، قبل إرسال أي شيء.

الاستجابة: OpenEmail::BatchResult

itemsArray<Hash>
Hash واحد لكل رسالة، بالترتيب الذي أرسلتها به. لا يُتراجع عن شيء، فهذا سجل لما حدث لكل رسالة لا تقرير عن معاملة. وتجيب API بـ 207 سواء قُبلت كل الرسائل أو بعضها أو لا شيء منها، فيعود الاستدعاء في كل الأحوال، و`status` الخاص بكل عنصر هو ما يجب التفرّع عليه.
sentInteger
كم عنصرًا قُبل، وهو ليس نفسه كم عنصرًا خرج. فقد يكون العنصر `ok` ومع ذلك يحمل `email` قيمة `status` فيه `failed` أو `partial`، لأن وسيلة نقل ترفض الرسالة بعد وجود السجل تمثل نتيجة تسليم لا طلبًا مرفوضًا.
failedInteger
كم عنصرًا يحمل `error`. والعدد فوق 0 قائمة يجب التصرف بشأنها لا سبب لإعادة إرسال الدفعة. فالرسائل المقبولة قد ذهبت بالفعل.

كل عنصر

indexInteger
الموضع الذي احتلته رسالة هذا العنصر في الـ Array التي أرسلتها. يُحمل كمفتاح إضافة إلى الترتيب، فتستطيع الشيفرة التي ترشّح `items` أو ترتّبها أن تعرف مع ذلك أي رسالة فشلت.
statusString
`ok` أو `error`. يحمل `ok` الحقل `email`، ويحمل `error` الحقل `error`، ولا يحمل أي عنصر الاثنين.
emailHash
الرسالة المقبولة، على عنصر `ok` فقط، بالشكل نفسه الذي يعيده إرسال واحد. وتكون قيمة `replayed` فيها true حين يطابق `Idempotency-Key` المشتق إرسالًا موجودًا بالفعل، فلم يُرسَل شيء جديد وهذه هي الرسالة الأصلية. ولا تحمل المفتاح `tracking`، لأن التفاعل يُبلَّغ عنه لاحقًا ولا يوجد ما يُبلَّغ عنه عند القبول.
errorHash
لماذا رُفضت هذه الرسالة وحدها، على عنصر `error` فقط. وهو غلاف الخطأ الخاص بـ API منقوصًا منه `docUrl` و`requestId`: فهذان يصفان الطلب، والطلب ككل قد نجح.

خطأ العنصر

typeString
الفئة التي يُتفرّع عليها: `validation_error` و`permission_error` و`not_found_error` و`conflict_error` وغيرها. والمجموعة مجمَّدة ولن تكبر، بخلاف `code`.
codeString
الفشل المحدد: `from_address_forbidden` و`invalid_email_address` و`too_many_recipients` و`reserved_header` و`message_too_large` و`unknown_parameter`. مفتوحة وقابلة للإضافة، فعامل أي رمز لا تعرفه معاملة `type` الخاص به.
messageString
جملة واحدة مكتوبة لإنسان، تسمي القيمة المخالفة حيث توجد واحدة. وليست معرّفًا ثابتًا. فرّع على `code`.
paramString
الحقل الذي رُفض، كمسار منقوط داخل تلك الرسالة: `to.0` أو `from` أو `attachments`. وغائب عندما لا يسمي الفشل أي حقل، ولا يُسبق أبدًا بموضع العنصر في الدفعة، فذاك ما يفيده `index`.