إرسال دفعة
`emails.send_batch`: حتى 100 رسالة، ونتائج لكل عنصر.
emails.send_batch
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`.