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

إرسال رسالة

POST /emails: رسالة واحدة، الآن أو لاحقًا.

POSTapi.openemail.uk/emails

ينفّذ الاستدعاء الحقيقي على مساحة عملك، بمفتاحك أنت.

الطلب

from مطلوب. وخلافًا للمحرّر لا يوجد مُرسِل احتياطي، لأن ذلك الاحتياطي هو العنوان الافتراضي لمساحة العمل، وهو يتغيّر في الخفاء كلما جاءت عناوين وذهبت أخرى.

الحقلمطلوبملاحظات
fromنعمعنوان مجرّد أو Name <addr>. ويجب أن يكون عنوانًا يحق للمفتاح الإرسال باسمه.
toنعمحتى 50 مستلِمًا موزّعين على to وcc وbcc مجتمعة.
cc, bccلالا يُذكر مستلِمو bcc أبدًا في البايتات التي يتلقاها أي شخص آخر.
subjectلاالقيمة الافتراضية فارغة.
html, textأحدهاوكلاهما معًا مقبول. وHTML هو ما يراه المستلِمون.
templateأحدها{ id, version?, props?, slots? }. نص مخزّن، بالمعرّف أو بالسبيكة. ويُرفض مع html أو text أو draftId. انظر الإرسال بقالب.
replyToلاعنوان واحد.
headersلاX-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority, Feedback-Id.
attachmentsلا{ filename, content, contentType } بصيغة base64، بحجم إجمالي 5 MB، أو { fileId } يسمّي ملفًا موجودًا في مساحة العمل. 20 ملفًا.
attachmentDeliveryلاmime أو link أو auto. وauto يحوّل الملفات إلى روابط بمجرد تجاوزها 2 MB على نطاق له نطاق ملفات نشط. والقيمة الافتراضية هي إعداد صندوق البريد.
threadIdلاالرد داخل محادثة قائمة.
draftIdلاإرسال مسودة قائمة.
scheduledAtلالحظة أو مدة بصيغة ISO. انظر الجدولة.
cancellableForSecondsلانافذة تراجع من 0 إلى 900 ثانية على إرسال فوري. وتُرفض مع scheduledAt، التي تظل قابلة للإلغاء حتى إرسالها. انظر الجدولة.
signatureلاfalse تترك التوقيع خارج هذه الرسالة. وإلا فهي تحمل توقيع العنوان المرسَلة منه، وهو توقيع ذلك العنوان نفسه أو التوقيع المضبوط لكل العناوين.
tagsلاحتى 10 تسميات من عندك. تُعاد كما هي ولا تُفسَّر أبدًا.
trackingلا{ opens?, clicks? }. كل منهما يتجاوز الإعداد لهذه الرسالة؛ وإذا حذفت حقلًا عاد ذلك النصف إلى إعداد العنوان المرسَلة منه، أو إلى إعداد كل العناوين، وهو مفعّل ما لم يُطفئه أحدهما.
translateلا{ to, from?, subject?, includeOriginal? }. يرسلها بلغة المستلِم. ويُحسم عند قبول الطلب، ويُرفض مع draftId.

الحقول غير المعروفة تُرفض بدل تجاهلها، فالاسم المكتوب خطأً هو 422 الآن بدل مفاجأة لاحقًا. والترويسات التي تُبطل تخويل المرسِل (From وSender وBcc وMessage-ID وReturn-Path وغيرها) تُرفض بـ reserved_header.

الاستجابة

200 حين تكون الرسالة قد ذهبت بالفعل، و202 حين يبقى شيء ينبغي أن يحدث لها. والمستدعي الذي يفرّع على رمز الحالة مصيب في الحالتين.

200 OK
{  "object": "email",  "id": "msg_c5f21cc6bfec4e848caf905b",  "status": "sent",  "mode": "live",  "from": "[email protected]",  "subject": "Your September invoice",  "messageId": "<2598…@acme.com>",  "transport": "ses",  "sentAt": "2026-08-29T08:19:08.000Z",  "source": "api",  "replayed": false}

id هو المقبض الدائم الذي تحتفظ به، وهو الذي يعود عليه حدث التسليم، إذ يسمّيه webhook الارتداد emailId. أما messageId فهو Message-ID بحسب RFC 5322 ويكون null حتى يوجد الـ MIME. لا تربط عليه: فخدمة الإرسال تعيد كتابة تلك الترويسة في طريق الخروج، ولذلك لا تظهر القيمة هنا في أي تقرير ارتداد أو تسليم ولا تتحقق مطابقة عليها أبدًا.

بلغة المستلِم

translate يكتب الرسالة بلغة شخص آخر قبل أن تذهب. فالنص، والموضوع ما لم تعطّل ذلك، يُترجَم لحظة قبول الطلب، وهي القاعدة نفسها التي يتبعها template وهي محورية للأسباب نفسها: فالرسالة المجدولة تحمل الكلمات التي أُقرّت لا ما ينتجه نموذج يوم الثلاثاء، والترجمة التي تعذّر إنتاجها ترفض الإرسال قبل وجود أي صف. ولا يُسلَّم شيء بلغة لم يخترها مرسِله.

translate

tostringمطلوب
اللغة المراد الكتابة بها: رمز BCP-47 (`de`) أو اسم إنجليزي ("German") أو اسم اللغة بلسانها ("Deutsch")، من حرفين إلى 60 حرفًا. وتُطبَّع الصيغ الثلاث إلى رمز الجدول قبل أي شيء آخر، فتكون طلبًا واحدًا، وهذا مهم لأن بصمة Idempotency-Key تؤخذ على الطلب بعد تحليله. وتُحسم المرادفات كذلك: `zh-TW` تصير `zh-Hant`. وما لا يُحسم إلى شيء هو 422 على `translate.to`.
fromstring
ما كتبت به، بأي من الصيغ الثلاث نفسها. وهو تحسين محض. فإن تركته، قُرئ النص واستُنتجت لغته، وذلك يكلّف استدعاء نموذج قصيرًا واحدًا. ويستحق التصريح به على مسار عالي الحجم، ويستحق التصريح به حين يكون النص في معظمه أسماءً وأرقامًا وروابط: إذ يمتنع الكشف عن الحكم بدل التخمين، ولا يكلّفك المصدر غير المحدَّد سوى اللغة المذكورة في التعليق فوق نصك الأصلي. وهو ليس `from` العلوي الذي يمثّل عنوانًا.
subjectboolean
ترجمة سطر الموضوع أيضًا. القيمة الافتراضية true؛ وfalse ترسل الموضوع كما كتبته تمامًا.
includeOriginalboolean
ضع ما كتبته فعلًا أسفل الترجمة، خلف فاصل ومع تعليق بلغة المستلِم. القيمة الافتراضية true، ويُستحسن إبقاؤها. فهي الشيء الوحيد الذي يتيح للقارئ التحقق من جملة وقعت وقعًا غريبًا بدل أن يُطلب منه الوثوق بنموذج لا يرى أيٌّ منكما مخرجاته.
curl
curl -X POST "$OE/emails" -H "$AUTH" -H "Content-Type: application/json" \  -d '{    "from": "[email protected]",    "to": ["[email protected]"],    "subject": "Your September invoice",    "html": "<p>Invoice attached. Payment is due on the 14th.</p>",    "translate": { "to": "de" }  }'
200 OK
{  "object": "email",  "id": "msg_c5f21cc6bfec4e848caf905b",  "status": "sent",  "from": "[email protected]",  "subject": "Ihre Rechnung für September",  "translation": {    "language": "de",    "languageName": "German",    "detectedSourceLanguage": "en",    "subject": true,    "includeOriginal": true  }}

translation حقل إضافي ولا يظهر إلا على رسالة جرت ترجمتها: على هذه الاستجابة وعلى GET /emails/{id}، ولا يظهر أبدًا على صف في قائمة، لأن القائمة لا تجلب الطلب المخزّن وصمتها هناك لا يقول شيئًا في أي اتجاه. وهو يحمل رموزًا لا صفوف لغات كاملة: فهو سجل لما جرى، وGET /languages هو موضع الاسم الأصلي للغة. وsubject في الاستجابة هو الموضوع المترجَم، فلا تسرد وحدة تحكم رسالة تحت نص لم يره المستلِم قط.

  • يعمل مع template، وهذه هي الحالة المفيدة: فالمخرَج المُصيَّر هو ما يُترجَم، ومن ثم يخدم نص مخزّن واحد كل لغة يقرأ بها عملاؤك. والقالب الذي يُصيّر مستندًا كاملًا يُفكَّك أولًا: فلا يصل إلى النموذج إلا ما بداخل <body>، ثم يُعاد وضع doctype وكتل <style> وقواعد @font-face حول الجواب. ولهذا أيضًا يقيس حد الـ 30,000 حرف النص لا المستند: فالرسالة المكوّنة من سطرين والملفوفة في ورقة أنماط بعلامة تجارية تبقى رسالة من سطرين.
  • الجزء الوحيد من قالب يبقى بلا ترجمة هو <title>، وهو ما لا يعرضه أي عميل بريد. أما <Preview> في react-email فيُصيَّر داخل النص ويُترجَم مع بقيته.
  • يُرفض مع draftId: أي 422 على translate، نصه "A draft is sent as it was written; translate a body or send a draft, not both". فالمسودة كتبها شخص وتُرسَل كما تركها.
  • ليست جزءًا من بصمة عدم التكرار عن قصد. فما يُجزَّأ هو الطلب الذي أرسلته، بما فيه translate؛ أما ما أنتجه النموذج فلا. ومن ثم فإن إعادة محاولة إرسال لم يُجَب عنه بالمفتاح Idempotency-Key نفسه تعيد تشغيل الأصل. فتعود الرسالة الموجودة بالفعل، بلا إرسال ثانٍ وبلا ترجمة ثانية. أما تجزئة الصياغة فكانت ستجعل إعادة محاولة نزيهة تُبصم بصمة مختلفة في كل مرة، وهكذا تخرج الرسالة نفسها مرتين.
  • الرسالة المترجَمة التي في الطابور أو المجدولة مجمّدة ضد تغيير الصياغة. انقل موعدها أو ألغِها؛ فتغيير ما تقوله يعني الإلغاء ثم الإرسال من جديد، أمام شخص يستطيع قراءة الكلمات الجديدة.
  • اللغة الهدف التي تُكتب من اليمين إلى اليسار تُنتَج كذلك: الترجمة ملفوفة في dir="rtl"، ونصك الأصلي تحتها موجّهًا باتجاهه هو. وتنجو السمة من منقّي الصادر، الذي يسمح بـ dir لهذا السبب بالذات، فتحمل الرسالة على الشبكة الاتجاه الذي أظهرته المعاينة.
الرمزالحالةمتى
`invalid_parameter`422translate.to أو translate.from تسمّي لغة لا نستطيع تحديدها. والرسالة تذكر الصيغ الثلاث المقبولة وتحيل إلى GET /languages.
`unknown_language`422الإخفاق نفسه ملتقَطًا بخطوة لاحقة، من الخدمة لا من المخطط. صمام أمان، على translate.to.
`translation_too_long`422أكثر من 30,000 حرف عند أي من طرفي استدعاء النموذج. رفض لا اقتطاع: فنصف رسالة مترجَمة لا يحمل أثرًا يبيّن أين توقفت، والقارئ يتصرف بناءً على النصف الذي أُعطيه.
`translation_not_configured`409مساحة العمل بلا مفتاح ذكاء اصطناعي والذكاء الاصطناعي على مستوى المنصة مطفأ. و409 لا 503 لأن إعادة المحاولة تخفق بالطريقة نفسها. ولم يُرسَل شيء. أرسِل بلا translate إن كنت تقصد إرسالها كما هي مكتوبة.
`translation_failed`503لم يُجب المزوّد، أو أجاب بما لا يصلح. ولم يُرسَل شيء؛ ولا تُنشر الرسالة بلا ترجمة كبديل أبدًا. وهذا الإخفاق من عندنا ويستحق إعادة المحاولة.
`unknown_parameter`422مفتاح غير معروف داخل translate، وهو كائن صارم كبقية الطلب.

الإرسال من الكود لا يقرأ الترجمة فيه أحد أولًا. وPOST /emails/translate هو الرحلة نفسها موقوفة قبل خطوة، لعرض ما يوشك شخص على إرساله عليه. ثم أرسِل ما أقرّه بوصفه html/subject عاديين بلا translate على الطلب إطلاقًا.