البث
`broadcasts.preview` و`send` و`list` و`list_all` و`iterate` و`get` و`list_recipients` و`list_all_recipients` و`iterate_recipients` و`get_recipient` و`stats` و`analytics` و`cancel`.
كل الدوالّ
draft = { audienceIds: ["aud_4c1b8e2a7d9f05c36b4e8a71"], from: "Acme <[email protected]>", subject: "{{firstName|Hello}}, the September release is out", html: "<p>Hi {{firstName|there}},</p><p>Here is what changed this month.</p>", text: "Hi {{firstName|there}}, here is what changed this month. Unsubscribe: {{unsubscribeUrl}}", tags: {campaign: "release-2026-09"}} reach = client.broadcasts.preview(draft)puts reach[:recipients], reach[:unsubscribed], reach[:suppressed] broadcast = client.broadcasts.send(draft) latest = client.broadcasts.get(broadcast[:id])while %w[scheduled queued sending].include?(latest[:status]) sleep 5 latest = client.broadcasts.get(broadcast[:id])end client.broadcasts.iterate_recipients(broadcast[:id]) do |copy| puts copy[:email], copy[:status], copy[:opens], copy[:clicks]end bounced = client.broadcasts.list_recipients(broadcast[:id], filter: "bounced")bounced.items.each { |row| puts "#{row[:emailId]} #{row[:email]}" } copy = client.broadcasts.get_recipient(broadcast[:id], "msg_01dad25067bc4dac966d515d")puts copy[:subject], copy[:bouncedAt] stats = client.broadcasts.stats(broadcast[:id], grain: "day")puts stats.dig(:totals, :opened), stats.dig(:totals, :clicked), stats.dig(:totals, :unsubscribed) lately = client.broadcasts.stats(broadcast[:id], days: 1)puts lately.dig(:window, :opened) later = client.broadcasts.send(draft, scheduledAt: "P1D")client.broadcasts.cancel(later[:id]) history = client.broadcasts.list(audience_id: draft[:audienceIds].first)puts latest[:status], latest.dig(:counts, :sent), history.items.size month = client.broadcasts.analytics(days: 30)month[:broadcasts].each do |row| puts row[:subject], row[:sent], row[:opened]endالبث يرسل رسالة واحدة إلى كل من في جمهور أو أكثر، بنسخة منفصلة لكل شخص. لكل نسخة مستلم واحد بالضبط بلا cc ولا bcc، فلا يرى أحد لمن ذهبت أيضًا، وكل نسخة رسالة عادية لها معرّف msg_ وأحداث وتتبّع وwebhooks خاصة بها. ويسردها list_recipients مع ما حدث لكل منها. ولا تُحفظ النسخ في مجلد المرسلة، لأن البث هو السجل.
يعود send فورًا والبث في الحالة queued، أو scheduled حين تمرّر scheduledAt:، ويجري الإرسال في الخلفية. ويتطلب send كلًّا من emails:send وaudiences:read، ويتطلب preview النطاق audiences:read. ويتطلب list وlist_all وiterate وget وlist_recipients وlist_all_recipients وiterate_recipients وget_recipient وstats وanalytics النطاق emails:read، ويتطلب cancel النطاق emails:send.
يحمل كل send ترويسة Idempotency-Key، إما مفتاحك عبر idempotency_key: وإما مفتاحًا يصنعه الـ gem، فإعادة المحاولة بعد فشل في الشبكة تجيب بالبث الذي أنشأته المحاولة الأولى، مع replayed مضبوطًا على true، بدل الإرسال مرتين. وpreview وget وcancel وكل قراءة آمنة للتكرار وتُعاد محاولتها.
broadcast = client.broadcasts.send( audienceIds: ["aud_4c1b8e2a7d9f05c36b4e8a71"], from: "Acme <[email protected]>", subject: "Doors open on Friday", text: "Hi {{firstName|there}}, doors open at nine. Unsubscribe: {{unsubscribeUrl}}", scheduledAt: Time.now + 3600, idempotency_key: "doors-open-2026-10") puts broadcast[:id], broadcast[:status], broadcast[:replayed]حقول البث وسائط مسمّاة أو Hash واحد، وتحتفظ بأسماء API بصيغة camelCase (audienceIds: وscheduledAt:). وidempotency_key: وapi_key: خياران للاستدعاء ولا يُرسلان أبدًا كحقول. والوسائط المسمّاة الممرَّرة إلى جانب Hash تُدمج فيه، فيرسل send(draft, scheduledAt: "P1D") المسودة نفسها بعد يوم. ويأخذ scheduledAt: قيمة Time أو DateTime أو سلسلة ISO 8601 أو مدة مثل PT2H، ويخرج Time كلحظة UTC. ولا يرسل preview مما تعطيه سوى audienceIds، فهو يأخذ الـ Hash نفسه الذي يأخذه send. والاستجابة Hash بمفاتيح من نوع Symbol، فيقرأ broadcast[:status] الحالة.
حقول الدمج
تُملأ subject وhtml وtext لكل شخص من جهة اتصاله. {{firstName}} هي الكلمة الأولى من اسم جهة الاتصال، و{{lastName}} بقيته، و{{name}} الاسم كاملًا، و{{email}} العنوان الذي تذهب إليه النسخة، و{{unsubscribeUrl}} الرابط الذي يلغي اشتراكه.
يقبل كل حقل قيمة بديلة بعد شرطة تُستخدم حين لا تكون لجهة الاتصال قيمة له، فيصير {{firstName|there}} "there" لجهة اتصال محفوظة بلا اسم. تُهرَّب القيم في html، ويُترك أي {{…}} آخر كما كُتب تمامًا.
مرّر template: بدل html: وtext: لإرسال قالب مخزَّن، في صورة Hash فيه id واختياريًا version وprops وslots. وتصل إليه القيم الخمس نفسها كخصائص، لكن فقط الخصائص التي يعلنها القالب، فالقالب الذي يعلن firstName يحصل عليه، والذي لا يعلنه لا يُرفض أبدًا بسببه. وأي شيء في props الخاصة به يذهب إلى كل نسخة بالتساوي.
إلغاء الاشتراك
تحمل كل نسخة ترويسات إلغاء الاشتراك بنقرة واحدة التي تتيح لبرنامج البريد عرض زر إلغاء الاشتراك الخاص به، وهو ما يطلبه كبار مزودي صناديق البريد من البريد الجماعي. والمتن html أو text الذي لا يضع {{unsubscribeUrl}} بنفسه يحصل على تذييل من سطر واحد فيه الرابط. أما القالب فيُرسل كما هو تمامًا، فضع {{unsubscribeUrl}} في القالب.
إلغاء الاشتراك يعلّم الشخص كملغٍ للاشتراك في كل جمهور ذهب إليه ذلك البث، ويُظهر audiences.list_contacts ذلك في unsubscribedAt الخاص بصفه، كما تصف صفحة الجماهير. ويبقى في الجمهور وفي دفتر العناوين، ولا تُمس جماهيره الأخرى، ويظل البريد المرسل إليه رسالةً رسالة يخرج. وإخراجه من الجمهور ثم إضافته من جديد يعيد اشتراكه.
من يُتخطى
يصل البث إلى كل جهة اتصال في واحد على الأقل من audienceIds، مرة واحدة مهما كان عدد ما يضمها منها. ويتخطى جهة الاتصال التي ألغت اشتراكها في كل جمهور من تلك الجماهير تنتمي إليه، والعنوان الموجود في قائمة الحظر بعد ارتداد أو شكوى أو لأن أحدًا أضافه إليها. وجهة الاتصال المضافة إلى أحد الجماهير بعد send وقبل أن يصل إليها الإرسال تُشمل.
يعيد preview الأرقام نفسها دون إرسال: recipients وunsubscribed وsuppressed. وsend الذي لن يصل إلى أحد يرفع 422 no_recipients في صورة OpenEmail::ValidationError.
يُقارن الإرسال كله بعدد الرسائل الشهري في الخطة قبل كتابة أي شيء، فالبث الذي لا تغطيه الحصة يرفع 429 send_quota_exceeded في صورة OpenEmail::RateLimitError ولا يترك شيئًا خلفه. وتُحسب كل نسخة رسالة واحدة.
الحالة والتقدم
يقرأ get قيم counts مباشرة من النسخ، فاستطلعه أثناء إرسال البث، مع sleep بين الاستدعاءات كما يفعل المثال أعلاه. تنتقل status من scheduled أو queued إلى sending وتستقر على sent حين تخرج كل نسخة سُلّمت أو تفشل. وتبقى sending ما دامت نسخ تنتظر، حتى بعد أن يقول completedAt إن آخر شخص قد وُصل إليه. وfailed تعني أن البث كله توقف، ويقول lastError السبب: لم يعد ممكنًا الإرسال من عنوان from، أو توقف القالب عن الحل، أو نفدت الخطة في منتصف الطريق، أو ظل الإرسال نفسه يفشل، أو تعذرت كتابة أي نسخة.
يوقف cancel بثًا حالته scheduled أو queued أو sending. لا يُضاف أحد آخر وتُلغى كل نسخة ما زالت تنتظر، بينما النسخ التي خرجت لا يمكن استرجاعها. وبمجرد أن تخرج كل النسخ يرفع cancel الخطأ 409 broadcast_not_cancellable في صورة OpenEmail::ConflictError، وإلغاء بث مُلغى يعيده كما هو.
من وصل إليهم
يعيد list_recipients صفحة OpenEmail::Page واحدة من الأشخاص الذين ذهب إليهم البث، صفًا لكل نسخة، مرتّبة حسب العنوان، مع items وhas_more? وnext_cursor. ويمر list_all_recipients على كل الصفحات في Array واحدة، ويمرّر iterate_recipients نسخة واحدة في كل مرة إلى كتلة، ولا يجلب الصفحة التالية إلا حين تطلبها الحلقة. ودون كتلة يعيد Enumerator. ويتراوح limit: من 1 إلى 200 والافتراضي 50، ويُعاد cursor: مع filter: وq: نفسيهما.
| `filter:` | يحتفظ بـ |
|---|---|
| pending | نسخ ما زالت في الطابور أو مجدولة أو قيد الإرسال. |
| sent | نسخ خرجت. |
| delivered | نسخ قبلها الخادم المستقبِل. |
| opened | نسخ فُتحت مرة واحدة على الأقل. |
| not_opened | نسخ أُرسلت ولم تُفتح قط. |
| clicked | نسخ فيها نقرة متتبَّعة واحدة على الأقل. |
| bounced | نسخ ارتدّت. |
| complained | نسخ أبلغ عنها الشخص كرسالة مزعجة. |
| failed | نسخ فشلت أو أُلغيت. |
| unsubscribed | أشخاص ألغوا اشتراكهم بعد خروج البث. |
يسمّي OpenEmail::BROADCAST_RECIPIENT_FILTERS كل مرشِّح، ويبحث q: في العنوان والاسم، متجاهلًا حالة الأحرف. وتستبعد مرات الفتح والنقرات وكلاء الصور وأدوات فحص الروابط، وتبقى 0 حين يخرج البث والتتبّع معطَّل.
يعيد get_recipient(id, email_id) نسخة واحدة: الصف نفسه، إضافة إلى subject وhtml وtext كما تلقّاها ذلك الشخص تمامًا، مع ملء حقول الدمج ورابط إلغاء الاشتراك الخاص به. مرّر emailId الخاص بالصف بوصفه email_id. وHTML من قبل إضافة تتبّع الفتح والنقر. وemail_id الذي ليس نسخة من هذا البث يرفع 404 recipient_not_found، والبث غير المعروف يرفع 404 broadcast_not_found، وكلاهما في صورة OpenEmail::NotFoundError.
يعيد stats المجاميع وسلسلة زمنية. تعدّ totals النسخ sent وdelivered وbounced وcomplained وfailed، مع pending للنسخ التي ما زالت تنتظر، والأشخاص الذين opened وclicked وunsubscribed، مع opens وclicks كأعداد للأحداث. وseries متفرقة والأقدم أولًا، فاصل واحد لكل grain: (minute أو hour أو day، والافتراضي hour) حدث فيه شيء، مقسّمة في المنطقة الزمنية التي تبعد offset_minutes: شرق UTC. مرّر Time.now.utc_offset / 60 للمنطقة المحلية. وهي تعدّ كل شخص مرة واحدة، عند أول مرة حدث له ذلك، فيتطابق مجموعها مع المجاميع.
مرّر days: أو minutes: إلى stats لتقرأ أيضًا ما حدث مؤخرًا. فيعدّ window عندئذ ما سُلّم، وما ارتد، وما أُبلغ عنه كبريد مزعج، وما فُتح، وما نُقر عليه، وما أُلغي الاشتراك فيه داخلها، ولا تحتفظ series إلا بفواصلها، بينما تظل totals تغطي البث كله. ودون أيٍّ منهما يكون window بقيمة nil.
المفتاح المقيّد بعناوين أو نطاقات بعينها لا يصل إلا إلى البثوث المرسلة من عنوان أو نطاق يملكه. ويستبعد list وlist_all وiterate البقية، ويرفع get وتوابع المستلمين وstats وcancel الخطأ 404 broadcast_not_found لها.
الاستجابة: بث
يعيد كلٌّ من send وget وcancel واحدًا من هذه، أي Hash بمفاتيح من نوع Symbol، ويضيف send الحقل replayed. ويعيد list صفحة OpenEmail::Page منها، من الأحدث، ويمر list_all وiterate على كل الصفحات. ويعيد preview Hash فيه audienceIds وrecipients وunsubscribed وsuppressed. ويعيد list_recipients صفحة OpenEmail::Page من صفوف المستلمين، ويعيد get_recipient صفًا واحدًا مع محتواه، ويعيد stats Hash فيه broadcastId وgrain وtotals وwindow وseries. ويعيد analytics Hash فيه totals وseries وصف لكل بث في broadcasts. والأوقات سلاسل ISO 8601 يحلّلها Time.iso8601.
idString- المعرّف الدائم، `brd_` يليه 24 حرفًا ست عشريًا.
statusString- `scheduled` أو `queued` أو `sending` أو `sent` أو `cancelled` أو `failed`. ويسمّي `OpenEmail::BROADCAST_STATUSES` كلًّا منها.
modeString- `live` أو `test`، بحسب المفتاح الذي أنشأه. ونسخ البث التجريبي تُعلَّم كمرسلة ولا تُسلَّم إلى أحد.
sourceString- من أين بدأ: `api` لمفتاح، و`oauth` لتطبيق متصل، و`composer` للتطبيق، و`mcp` لمساعد.
audienceIdsArray<String>- الجماهير التي أُرسل إليها، كل منها مرة واحدة.
fromString- العنوان الذي تُرسل منه كل نسخة.
subjectString- الموضوع كما كُتب، بحقول الدمج كلها. فارغ حين يوفر القالب الموضوع.
countsHash- `recipients` هو التقدير المأخوذ عند `send`. ويحسب `created` النسخ المكتوبة، و`skipped` الأشخاص الذين تُخطّوا لأن عناوينهم كانت محظورة حينها، و`failedToQueue` الأشخاص الذين تعذرت كتابة نسختهم. وتحسب `queued` و`sending` و`sent` و`failed` و`cancelled` النسخ حسب الحالة التي فيها كل منها الآن.
lastErrorString or nil- لماذا فشل البث، أو أحدث نسخة تعذّرت كتابتها ولماذا. ويكون nil ما دام لم يحدث خطأ.
scheduledAtString or nil- بصيغة ISO-8601 بتوقيت UTC، متى يُتوقع أن يبدأ الإرسال. ويكون nil لبث يُرسل فورًا.
startedAtString or nil- ISO-8601 UTC، وقت وصول الإرسال إلى أول الأشخاص.
completedAtString or nil- ISO-8601 UTC، وقت الوصول إلى آخر شخص. وقد تظل نسخ تنتظر الخروج بعده.
cancelledAtString or nil- ISO-8601 UTC، وقت إيقاف `cancel` له.
createdAtString- ISO-8601 UTC، وقت استدعاء `send`. ويحدد ترتيب القائمة.
updatedAtString- ISO-8601 UTC، يُحدَّث مع تقدم الإرسال.
الاستجابة: صف مستلم
كل صف من list_recipients وlist_all_recipients وiterate_recipients، في صورة Hash بمفاتيح من نوع Symbol. ويضيف الـ Hash الذي يعيده get_recipient الحقول subject وhtml وtext.
emailIdString- معرّف `msg_` لنسخة هذا الشخص. يقرؤها `get_recipient` مع محتواها، ويقرؤها `emails.get` كرسالة مرسلة، كما تصف صفحة السرد والجلب.
contactIdString or nil- جهة الاتصال التي ذهبت إليها، أو nil حين تكون جهة الاتصال قد حُذفت منذ ذلك الحين.
emailString- العنوان الذي ذهبت إليه النسخة.
nameString or nil- الاسم المسجّل في جهة الاتصال.
statusString- حالة النسخة: `queued` أو `scheduled` أو `sending` أو `sent` أو `failed` أو `cancelled`.
sentAtString or nil- ISO-8601 بتوقيت UTC، وقت خروج النسخة.
deliveredAtString or nil- ISO-8601 بتوقيت UTC، وقت قبول الخادم المستقبِل لها، أول `email.delivered`.
bouncedAtString or nil- ISO-8601 بتوقيت UTC، وقت ارتدادها، أول `email.bounced`.
complainedAtString or nil- ISO-8601 بتوقيت UTC، وقت إبلاغ الشخص عنها كرسالة مزعجة، أول `email.complained`.
failureString or nil- سبب فشل النسخة، إن فشلت.
opensInteger- الفتحات المسجّلة، دون تلك التي تحدثها وكلاء الصور والماسحات. 0 حين كان التتبع معطّلًا.
firstOpenAtString or nil- ISO-8601 بتوقيت UTC، أول فتح.
clicksInteger- النقرات المسجّلة على الروابط المتتبَّعة، دون الماسحات.
firstClickAtString or nil- ISO-8601 بتوقيت UTC، أول نقرة.
unsubscribedAtString or nil- بصيغة ISO-8601 بتوقيت UTC، متى ألغى هذا الشخص اشتراكه في أحد جماهير البث بعد خروجه، عبر رابطه أو بطريقة أخرى.