القوالب والقواعد وخطافات الويب
كل أمر `templates` و`rules` و`webhooks`: أجسام محفوظة ترسلها بالمعرّف النصي، وقواعد ترتّب البريد الوارد، وأحداث موقّعة لخادمك الخاص.
ثلاث مساحات أسماء
تتيح مساحات الأسماء الثلاث هذه لصندوق البريد أن يعمل دون أن يراقبه أحد. يخزّن templates الأجسام التي ترسلها مرات كثيرة، ويرتّب rules البريد لحظة وصوله، ويخبر webhooks خادمك الخاص بما حدث. وكل أمر طريقة SDK باسمها بصيغة kebab-case، فيصبح webhooks.rotateSecret هو openemail webhooks rotate-secret، ويقرأ الوسائط والخيارات كأي أمر موارد آخر.
| مساحة الأسماء | أيضًا | القراءة تحتاج إلى | التغيير يحتاج إلى |
|---|---|---|---|
| templates | template | templates:read | templates:write، وemails:send أيضًا لـ send |
| rules | rule | rules:read، بما في ذلك test | rules:write |
| webhooks | webhook | webhooks:read | webhooks:write، بما في ذلك test وreplay-delivery |
تسرد هذه الصفحة كل أمر وما يجدر معرفته قبل كتابة سكربت له. ولكل وسيط وخيار، بنوعه والنطاقات التي يحتاجها ونقطة النهاية وما يعيده، شغّل openemail <namespace> <verb> --help. أضف --json لتحصل على الصفحة نفسها بصيغة JSON.
openemail templates --helpopenemail rules create --helpopenemail webhooks replay-delivery --help --jsonالقوالب
أجسام تُخزَّن مرة وتُرسل مرات كثيرة، مع إصدارات ومعاينات وخصائص ذات أنواع. كل أمر يأخذ <id-or-slug> يقبل معرّف tpl_ أو المعرّف النصي. والمعرّف النصي لا يتغير أبدًا عند إعادة تسمية القالب، لذا ثبّته في السكربتات.
| الأمر | ما تفعله |
|---|---|
| openemail templates list | اسرد القوالب، الأحدث تحديثًا أولًا. يُبقي --status المسودات أو النشطة أو المؤرشفة، ويطابق --search الأسماء والمعرّفات النصية والمواضيع، ويختار --sort الترتيب |
| openemail templates get <id-or-slug> | اقرأ قالبًا مع إصداره الرئيسي كاملًا، بما فيه الجسم |
| openemail templates create --name <value> | أنشئ قالبًا وإصداره الأول. يبقى مسودة ما لم تمرّر --publish، ويبذره --starter من تصميم مبدئي |
| openemail templates update <id-or-slug> | عدّل الاسم أو المعرّف النصي أو الوصف أو الحالة، أو جسم المسودة. وتبقى عمليات الإرسال على الإصدار المنشور حتى تنشر |
| openemail templates duplicate <id-or-slug> | انسخ الإصدار الرئيسي إلى قالب جديد، يبدأ مسودة |
| openemail templates replace-content <id-or-slug> | استبدل الجسم بجسم تصميم مبدئي (--starter) أو قالب آخر (--from-template-id). يطلب منك التأكيد |
| openemail templates delete <id-or-slug> | احذف قالبًا وكل إصداراته. يطلب منك التأكيد |
| openemail templates list-versions <id-or-slug> | اسرد الإصدارات، الأحدث أولًا، دون أجسامها |
| openemail templates get-version <id-or-slug> <version> | اقرأ إصدارًا واحدًا مع جسمه، دون المساس بالمسودة |
| openemail templates publish <id-or-slug> | انشر المسودة كي تُحال إليها عمليات الإرسال. ونشر إصدار رئيسي منشور بالفعل لا يغيّر شيئًا |
| openemail templates restore-version <id-or-slug> <version> | أعِد جسم إصدار أقدم ليصبح المسودة. يطلب منك التأكيد |
| openemail templates delete-version <id-or-slug> <version> | احذف إصدارًا واحدًا. يُرفض الإصدار المنشور والإصدار الرئيسي والإصدار الوحيد. يطلب منك التأكيد |
| openemail templates list-starters | اسرد التصاميم المبدئية المدمجة |
| openemail templates get-starter <slug> | اقرأ تصميمًا مبدئيًا واحدًا كاملًا، مع شجرة كتله ومعاينة معروضة |
| openemail templates list-fonts | اسرد خطوط الويب التي قد يحمّلها القالب |
| openemail templates render | اعرض جسمًا غير مخزّن في أي مكان، من --html أو --document |
| openemail templates preview <id-or-slug> | اعرض قالبًا مخزّنًا مع --props و--slots، بما فيه المسودات، دون إرساله |
| openemail templates get-analytics <id-or-slug> | عمليات الإرسال والفتح والنقر خلال فترة، حسب اليوم وحسب المصدر وحسب الإصدار |
| openemail templates list-sends <id-or-slug> | الرسائل الفردية التي أرسلها القالب، الأحدث أولًا، صفحةً صفحة |
| openemail templates send <id-or-slug> --from <value> --to <a,b> | أرسل بريدًا معروضًا من الإصدار المنشور، أو من الإصدار الذي يثبّته --template-version |
للقالب إصدار رئيسي، وهو مسودة ما دامت فيه تعديلات غير منشورة، وإصدار منشور، وهو ما يستخدمه الإرسال دون --template-version. وcreate دون --publish، وتعديل الجسم بـ update، وreplace-content وrestore-version كلها تكتب المسودة، فلا يرى المستلمون شيئًا جديدًا حتى publish.
- القالب المؤرشف يرفض الإرسال بـ
template_archived. ويعيدهpublishنشطًا. - تضم مساحة العمل 200 قالب على الأكثر، بما فيها المؤرشفة، لذا فالحذف هو الطريقة الوحيدة لإفساح المجال.
- يُرفض
deleteبـtemplate_in_useما دام بث مجدول أو في الطابور يسمّي القالب.
القواعد
شروط وإجراءات تُقيَّم على البريد الوارد، بالترتيب الذي يعرضه rules list. ولا تعمل القاعدة إلا على البريد الذي يصل وهي مفعّلة. ولا يوجد أمر يطبّق قاعدة على بريد موجود بالفعل في صندوق البريد، وrules test هو طريقتك لترى ما ستلتقطه. تبدأ معرّفات القواعد بـ rul_.
| الأمر | ما تفعله |
|---|---|
| openemail rules list | اسرد القواعد بالترتيب الذي تعمل به. ويُبقي --enabled أو --no-enabled نوعًا واحدًا |
| openemail rules get <id> | اقرأ قاعدة واحدة، مع matchCount وlastMatchedAt |
| openemail rules create --name <value> --conditions <json|@file|-> --actions <json|@file|-> | أنشئ قاعدة في آخر الترتيب. تكون مفعّلة ما لم تمرّر --no-enabled |
| openemail rules update <id> | غيّر قاعدة. يستبدل --conditions و--actions القائمة كلها، وينقل --position هذه القاعدة وحدها |
| openemail rules delete <id> | احذف قاعدة. يبقى ما فعلته بالفعل في list-runs. يطلب منك التأكيد |
| openemail rules reorder <rule-ids...> | اضبط ترتيب كل القواعد دفعة واحدة، مع ذكر كل قاعدة مرة واحدة بالضبط |
| openemail rules test <id> | شغّل قاعدة تشغيلًا تجريبيًا على بريد موجود بالفعل في صندوق البريد. لا تغيّر شيئًا، وتعمل على قاعدة معطّلة |
| openemail rules list-runs | ما فعلته القواعد فعلًا بالبريد الوارد، الأحدث أولًا. ويضيّقه --rule-id و--thread-id |
--conditions قائمة من كائنات { field, op, value }، يربطها --match all أو --match any، حيث value سلسلة نصية دائمًا وnegate: true يعكس شرطًا واحدًا. و--actions قائمة من كائنات { type, value } تُطبَّق بالترتيب. تأخذ القاعدة من 1 إلى 20 شرطًا ومن 1 إلى 10 إجراءات، ويضم صندوق البريد 100 قاعدة على الأكثر.
- حقول الشروط:
fromوfrom_domainوenvelope_fromوtoوccوbccوrecipientوreply_toوdelivered_toوsubjectوbodyوheaderوlist_idوattachment_nameوattachment_typeوhas_attachmentوattachment_sizeوmessage_sizeوspamوhourوweekday. - العوامل:
matchesوcontainsوequalsوstarts_withوends_withوgtوlt. ولا يعملgtوltإلا على الحقول الرقمية، ولا يأخذhas_attachmentوspamإلاequalsمعtrueأوfalse. - أنواع الإجراءات:
labelوremove_labelوarchiveوmark_readوstarوspamوtrashوforwardوreplyوblock_senderوreject. ويأخذlabelوremove_labelمعرّف تسمية مثلUSER_RECEIPTS، ويأخذforwardعنوانًا، ويأخذreplyمعرّف قالب أو معرّفه النصي. - يطابق
from_domainالنطاقات الفرعية أيضًا، ويُقرأhourوweekdayبتوقيت UTC، مع0ليوم الأحد. - القاعدة التي فيها إجراء
rejectيجب أن تختبرenvelope_fromأيضًا، وإلا رُفضت بـreject_needs_envelope.
خطافات الويب
نقاط نهاية على خادمك الخاص تستقبل أحداث صندوق البريد الموقّعة، مع أسرار توقيعها وسجل تسليمها وسجل تدقيق لكل تغيير. تبدأ معرّفات نقاط النهاية بـ whe_ ومعرّفات التسليم بـ whd_.
| الأمر | ما تفعله |
|---|---|
| openemail webhooks list | اسرد نقاط النهاية في مساحة العمل، الأحدث أولًا، مع حالتها الصحية |
| openemail webhooks get <id> | اقرأ نقطة نهاية واحدة. سر التوقيع ليس جزءًا من أي قراءة أبدًا |
| openemail webhooks create --url <value> | سجّل نقطة نهاية HTTPS. يطبع سر التوقيع، وهي المرة الوحيدة التي ترى فيها ذلك السر |
| openemail webhooks update <id> | غيّر عنوان URL، أو الأحداث، أو قوائم السماح، أو حالة التفعيل. كل قائمة تستبدل المخزنة |
| openemail webhooks delete <id> | احذف نقطة نهاية وسجل تسليمها. يطلب منك التأكيد |
| openemail webhooks rotate-secret <id> | أصدر سر توقيع جديدًا. يتوقف القديم عن العمل فورًا. يطلب منك التأكيد |
| openemail webhooks test <id> | أرسل حدث email.sent اصطناعيًا موقّعًا وأبلغ عن كيفية سير التسليم |
| openemail webhooks list-deliveries <id> | محاولات التسليم لنقطة نهاية واحدة، الأحدث أولًا. ويضيّقها --status و--since و--until |
| openemail webhooks get-delivery <id> <delivery-id> | محاولة واحدة كاملة: الجسم المرسل، وجواب خادمك، وكل محاولة للحدث، وما إذا كانت إعادة الإرسال ستُقبل |
| openemail webhooks replay-delivery <id> <delivery-id> | أرسل حدثًا مخزنًا واحدًا إلى نقطة النهاية من جديد، الآن |
| openemail webhooks list-workspace-deliveries | محاولات التسليم عبر كل نقاط النهاية، أو تلك التي يسمّيها --endpoint-ids |
| openemail webhooks list-activity <id> | سجل التدقيق لنقطة نهاية واحدة: من أنشأها أو غيّرها أو اختبرها أو أعاد إرسالها أو أزالها |
| openemail webhooks list-workspace-activity | سجل التدقيق لكل نقطة نهاية، بما فيها المزالة |
أغفِل --event-types تستقبل نقطة النهاية المجموعة الافتراضية، وهي أحداث email.* عدا email.replied. أما email.replied وأحداث domain.* وأحداث suppression.* فلا تصلها إلا حين تسمّيها. ويضيّق --address-allowlist و--domain-allowlist نقطة النهاية على بعض العناوين أو النطاقات، كما يضيّقان مفتاح API.
- تضم مساحة العمل 10 نقاط نهاية ما لم يرفع الدعم حدّها.
- نقطة النهاية التي تفشل في 100 تسليم متتالٍ يوقفها الخادم، ويعيدها
webhooks update <id> --enabled. - مع تسجيل الدخول عبر المتصفح، لا يستطيع قراءة تسليم بـ
get-deliveryإلا مالك مساحة العمل. وأي شخص آخر يحصل علىowner_onlyورمز الخروج4.
افحص قالبًا، ثم انشره
يعرض templates preview بالضبط ما سينتجه إرسال بالقيم نفسها، بما في ذلك المسودات، ولا يحتاج إلا إلى templates:read، فيستطيع حتى مفتاح القراءة وحدها تشغيله. ويبلّغ عن الخاصية المطلوبة الناقصة كتحذير حيث كان send سيرفضها، لذا أفشل البناء عند أي تحذير. وpublish آمن في كل نشر، لأن نشر إصدار رئيسي منشور بالفعل لا يغيّر شيئًا.
draft=$(openemail templates get order-shipped --json | jq .latestVersion)openemail templates preview order-shipped --template-version "$draft" \ --props '{"orderId":"AC-4192","customer":"Ada"}' --json | jq -e '.warnings == []'openemail templates publish order-shippedالإرسال من قالب
ثبّت الإصدار، كي لا تغيّر إعادة كتابة تُنشر غدًا ما يرسله هذا الكود، ومرّر مفتاح لاتكرارية مأخوذًا مما تسبب في الإرسال، كي تعيد المحاولةُ بعد ضياع الجواب تشغيلَ الرسالة الأولى بدلًا من إرسال ثانية. يطبع --dry-run الطريقة وعنوان URL والترويسات مع حجب بيانات اعتمادك والجسم، ولا يرسل شيئًا ويخرج بالرمز 0. شغّله مرة أخرى دون --dry-run لترسل.
openemail templates send order-shipped \ --from 'Acme <[email protected]>' \ --to [email protected] \ --template-version 5 \ --props '{"orderId":"AC-4192","customer":"Ada"}' \ --idempotency-key order-shipped:AC-4192 \ --dry-runاختبر قاعدة قبل أن تعمل
أنشئ القاعدة معطّلة، وشغّلها تشغيلًا تجريبيًا على البريد الحديث، ثم فعّلها حين تلتقط ما قصدته. مع تسجيل الدخول عبر المتصفح، يطلب rules create وrules update رمز تحقق لا يستطيع السكربت كتابته، لذا شغّل openemail verify أولًا. وخلال الدقائق الـ 60 التالية يشغّلهما ذلك الملف الشخصي دون سؤال.
[ { "field": "from_domain", "op": "equals", "value": "stripe.com" }, { "field": "has_attachment", "op": "equals", "value": "true" }][ { "type": "label", "value": "USER_RECEIPTS" }, { "type": "archive" }]openemail verifyrule=$(openemail rules create --name 'Stripe receipts' \ --conditions @conditions.json --actions @actions.json --no-enabled --json | jq -r .id)openemail rules test "$rule" --days 30 --limit 100openemail rules update "$rule" --enabledاقرأ تحذيرات rules test قبل تطابقاته. يعني field_unevaluable أن شرطًا يقرأ شيئًا لم يعد البريد المخزن يحمله، فلم يستطع الاختبار الحكم عليه، ويعني forward_unverified أن هدف إعادة التوجيه غير مستضاف هنا. وتسرد wouldApply ما تعلنه القاعدة: فإعادة التوجيه إلى عنوان لم يؤكد تظل تفشل حين يصل بريد حقيقي.
ضع قاعدة أولًا، واعرف لماذا انتقلت رسالة
يأخذ rules reorder كل قاعدة في صندوق البريد مرة واحدة بالضبط. والقاعدة المُغفلة أو المذكورة مرتين تُرفض ولا يتحرك شيء. ويعيد rules list المعرّفات بالترتيب الذي تعمل به، لذا ضع القاعدة التي تريدها أولًا أمام البقية.
first=rul_4f1c9a2b7d3e8f6a0b5c1d2eopenemail rules reorder "$first" $(openemail rules list --all --ndjson \ | jq -r --arg first "$first" 'select(.id != $first) | .id')openemail rules list-runs --thread-id CAHk7pQ2x9LmZ4 --json | jq '.items[] | {ruleName, actions, failures}'list-runs هو سجل ما حدث فعلًا. كل صف قاعدة واحدة طابقت رسالة واحدة، مع الإجراءات التي نفذت، وفي failures تلك التي رفضها صندوق البريد، مثل الرد على مرسل سبق الرد عليه ذلك اليوم. ويحتفظ كل صف بالاسم الذي كان للقاعدة حينها، لذا يعمل --rule-id مع قاعدة حذفتها منذ ذلك الحين.
سجّل خطاف ويب وأثبت أنه يعمل
يعرض webhooks create سر التوقيع مرة واحدة، ولا يعرضه أي أمر لاحق مرة أخرى. ومع --json يكون في JSON على stdout، بينما يذهب التذكير بحفظه إلى stderr، فيظل الناتج قابلًا للتحليل. ويرسل webhooks test حدث email.sent اصطناعيًا موقّعًا أيًّا كان ما تشترك فيه نقطة النهاية، ولا يُرسَل أي بريد.
openemail verifyopenemail webhooks create --url https://hooks.acme.com/openemail \ --event-types email.received,email.bounced,email.complained \ --description 'Support desk sync' --json > endpoint.jsonjq -r .secret endpoint.jsonopenemail webhooks test "$(jq -r .id endpoint.json)" --json | jq .deliveryrm endpoint.jsonضع السر في مخزن أسرارك قبل حذف الملف. يخرج test بالرمز 0 حتى حين يفشل خادمك، لذا اقرأ delivery.status: delivered لجواب 2xx وfailed لأي شيء آخر، بما فيه إعادة التوجيه، لأن إعادات التوجيه لا تُتبع أبدًا. وresponseCode بقيمة null يعني أنه لم يصل أي جواب على الإطلاق.
اعثر على التسليمات الفاشلة وأعِد إرسال أحدها
بعد انقطاع من جهتك، اسرد ما فشل عبر كل نقاط النهاية، وتحقّق من أن إعادة الإرسال ستُقبل، وأرسل الحدث من جديد. وتحمل إعادة الإرسال معرّف الحدث نفسه، فالمستقبِل الذي يُسقط المعرّفات التي عالجها بالفعل يعاملها كالحدث الذي يعرفه.
openemail webhooks list-workspace-deliveries --status failed --since 2026-09-26T00:00:00Z --all --ndjson \ | jq -r '[.endpointId, .id, .eventType, (.responseCode // "no answer")] | @tsv'openemail webhooks get-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28 --json | jq .replayRefusalopenemail webhooks replay-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28- يأخذ
--sinceو--untilلحظة بصيغة ISO 8601. - الصف الفاشل الذي تحمل
nextAttemptAtفيه وقتًا لا تزال أمامه إعادة محاولة تلقائية. - تكون
replayRefusalبقيمةnullحين ستخرج إعادة الإرسال، وإلا فإنها تسمّي سبب رفضها، مثلwebhook_disabledما دامت نقطة النهاية معطّلة. - تجري إعادات الإرسال حدثًا واحدًا في كل مرة. ولا يوجد أمر يعيد إرسال كل تسليم فاشل.
رموز التحقق
مع تسجيل الدخول عبر المتصفح، تطلب أربعة من هذه الأوامر رمز تحقق قبل أن تغيّر أي شيء، كما يفعل تطبيق الويب: rules create وrules update وwebhooks create وwebhooks update. ولا يُطلب من مفتاح API أبدًا. وكل أمر آخر في هذه الصفحة يعمل دون رمز، بما فيها عمليات الحذف وwebhooks rotate-secret.
- في الطرفية، ترسل إليك CLI بالبريد رمزًا من ست خانات، أو تطلب رمزًا من تطبيق المصادقة حين يكون تسجيل الدخول بخطوتين مفعّلًا، ثم تشغّل الأمر مرة واحدة.
- من دون إشراف، مع
--jsonأو--no-input، أو في CI، أو دون طرفية، لا أحد يستطيع كتابة الرمز، لذا يتوقف الأمر برمز الخروج4ولا يغيّر شيئًا. شغّلopenemail verifyأولًا، فلا يحتاج الملف الشخصي إلى رمز مدة 60 دقيقة. --yesيؤكد الحذف، لكنه لا يتخطى الرمز أبدًا.
التأكيدات والتشغيل التجريبي
سبعة أوامر هنا تزيل شيئًا أو تكتب فوقه، لذا تطلب منك التأكيد أولًا: templates delete وtemplates delete-version وtemplates replace-content وtemplates restore-version وrules delete وwebhooks delete وwebhooks rotate-secret. ومن دون إشراف يتوقف كل منها برمز الخروج 2 ما لم تمرّر --yes.
$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --no-input✗ Refusing to run unattended. Pass --yes to confirm.$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --yesيطبع --dry-run أول طلب كان سيغيّر شيئًا ويخرج بالرمز 0، دون إرساله أو طلب التأكيد منك. ومع --json يطبع مستند { dryRun, request } واحدًا. أما rules test وtemplates render وtemplates preview فلا تغيّر شيئًا، لكنها طلبات POST، لذا يطبعها التشغيل التجريبي بدلًا من تشغيلها.
التصفح
- تقرأ
templates listوtemplates list-versionsوrules listوrules list-runsوكل أمرwebhooks list…صفحة واحدة في كل مرة، 25 صفًا ما لم يطلب--limitحتى 100. وتعرض الطرفية قيمة--cursorالتي تمرّرها للصفحة التالية. - يقرأ
--allكل صفحة، ويتوقف--max <n>بعد هذا العدد من الصفوف، ويطبع--ndjsonكائن JSON واحدًا في كل سطر. ومع--jsonتطبع القائمة مستند{ items, hasMore, nextCursor }واحدًا، ومع--allكذلك. - أعِد المؤشر مع المرشّحات والترتيب نفسها التي جاء بها. وأي شيء آخر يُرفض بوصفه
invalid_cursor، برمز الخروج7. - أما
templates list-sendsفيتصفح بالأرقام بدلًا من ذلك، مع--pageو--page-size، ويبلّغ عنtotal، ولا يملك--all. وأرقام الصفحات تنزاح أثناء خروج البريد، لذا ضيّق الفترة بـ--daysأو--minutesبدلًا من التعمق في الصفحات. - يعيد
templates list-startersوtemplates list-fontsالفهرس كله دفعة واحدة، ويعيدrules reorderكل قاعدة في قائمة عادية بترتيبها الجديد. - يضم صندوق البريد 100 قاعدة على الأكثر، لذا يعيد
rules list --limit 100كل قاعدة في صفحة واحدة دائمًا.
خيارات تستحق نظرة ثانية
--template-versionهو حقل الجسمversion، أُعيدت تسميته لأن--versionيطبع إصدار CLI. والوسيط<version>فيget-versionوrestore-versionوdelete-versionرقم إصدار، لا معرّفtplv_.- تأخذ
--conditionsو--actionsو--documentو--slotsو--propsوبقية خيارات JSON قيمة JSON مضمّنة، أو من ملف عبر@path، أو من stdin عبر-. ويأخذ--dataالجسم كله بالطريقة نفسها، وأي خيار تمرّره إلى جانبه يتجاوز مفتاحه. - يأخذ
--htmlالترميز نفسه، لا ملفًا، لذا يرسل--html @page.htmlالنص@page.html. مرّر--html "$(cat page.html)"، أو ضعhtmlفي الملف الذي تعطيه لـ--data. - يستبدل
rules update --conditionsو--actionsالقائمة كلها، وكذلكwebhooks update --event-typesو--address-allowlistو--domain-allowlist. اقرأ القيمة الحالية، وغيّرها، وأرسلها كلها. - القيمة الفارغة لـ
--event-typesخطأ استخدام. ولإعادة نقطة نهاية إلى المجموعة الافتراضية، أرسل--data '{"eventTypes":[]}'، ولإيقاف تسليماتها مرّر--no-enabled. - يأخذ
--expected-versionفيtemplates updateوreplace-contentوrestore-versionالإصدار الرئيسي الذي قرأته. وحين يكون شخص آخر قد حرّك الإصدار الرئيسي منذ ذلك الحين، يتوقف الأمر برمز الخروج6وversion_conflict، ولا يكتب شيئًا. - يوقف
rules update <id> --no-enabledقاعدة ويحتفظ بمكانها في الترتيب، وهذه طريقة إيقاف قاعدة مؤقتًا دون حذفها.