MailLandDocs
البريد الوارد

ويب هوك البريد الوارد

استقبال بيانات البريد الوارد المُحلَّلة عبر إشعارات ويب هوك

عندما يطابق بريد وارد مسارًا تتضمن actions فيه قيمة webhook، يرسل MailLand البريد المُحلَّل إلى ويب هوك ذلك المسار كطلب POST بصيغة JSON. تغطي هذه الصفحة شكل الحمولة، والتحقق من التوقيع، وسلوك التسليم.

حمولة ويب هوك

يرسل MailLand طلب POST إلى عنوان URL الخاص بويب هوك مع نص JSON التالي:

Inbound webhook payload
{
  "event": "inbound.received",
  "event_id": "evt_a1b2c3d4e5f60718293a4b5c6d7e8f90",
  "timestamp": "2026-02-17T14:30:00Z",
  "occurred_at": "2026-02-17T14:29:58Z",
  "schema_version": 1,
  "data": {
    "idempotency_key": "k7m2x9q4p1w8z3_r7k3m9q2p4w8x1",
    "email": {
      "id": "k7m2x9q4p1w8z3",
      "received_at": "2026-02-17T14:29:58Z",
      "size_bytes": 48213,
      "envelope": {
        "from": "[email protected]",
        "to": ["[email protected]"]
      },
      "headers": {
        "from": { "address": "[email protected]", "name": "Jane Customer" },
        "to": [{ "address": "[email protected]", "name": null }],
        "cc": [],
        "subject": "Need help with my account",
        "message_id": "<[email protected]>",
        "in_reply_to": null,
        "reply_to": "[email protected]",
        "date": "2026-02-17T14:29:55Z"
      },
      "body": {
        "text_preview": "I need assistance with my account settings.",
        "has_text": true,
        "has_html": true
      },
      "attachments": [
        {
          "id": "a3k7m2q9p4w1x8",
          "file_name": "screenshot.png",
          "content_type": "image/png",
          "size_bytes": 34500,
          "is_inline": false,
          "content_id": null,
          "download_url": "https://r2.example.com/screenshot.png?X-Amz-Signature=..."
        }
      ],
      "authentication": {
        "spf": { "result": "pass" },
        "dkim": { "result": "pass" },
        "dmarc": { "result": "pass" }
      },
      "spam": {
        "score": 1.2,
        "is_spam": false,
        "reason": null
      },
      "source": {
        "ip": "203.0.113.42",
        "hostname": "mail.example.com"
      }
    },
    "matched_route": {
      "id": "r7k3m9q2p4w8x1",
      "name": "Support Inbox"
    }
  }
}

لا يُدرج نص البريد الكامل داخل الحمولة أبدًا: فقط معاينة مختصرة text_preview وعلامتا has_text/has_html. لا يوجد حقل body.html.

الحقول التي لا قيمة لها (name، in_reply_to، content_id، reason، data.matched_route، وغيرها) تُحذف من JSON تمامًا بدلًا من إرسالها كـnull. يعرض المثال أعلاه null من أجل الوضوح؛ لا تعتمد على وجود المفتاح.

حقول الحمولة

الحقلالنوعالوصف
eventstringدائمًا inbound.received
event_idstringمعرّف الحدث، ويساوي ترويسة X-MLND-Event-Id. وهو ثابت لكل رسالة واردة ولكل ويب هوك (evt_ متبوعًا بـ 32 رمزًا سداسيًا عشريًا): كل إعادة محاولة وكل إعادة نشر لنفس التسليم تحمل نفس event_id والترويسة والمحتوى. تحمل إعادة التشغيل اليدوي event_id جديدًا وتضبط data.replay_of.
data.idempotency_keystring{emailPublicId}_{routePublicId} عند مطابقة مسار، أو {emailPublicId} فقط إذا لم يطابق أي مسار (أو لم يعد المسار موجودًا). ثابت عبر إعادة المحاولات وإعادة التشغيل اليدوي
data.replay_ofstringيظهر فقط في إعادة التشغيل اليدوي: قيمة event_id التي حملها التسليم الأصلي. لا يُرسَل في التسليمات العادية ولا في إعادة تشغيل رسائل وصلت قبل تثبيت معرّفات الأحداث
timestampstringطابع زمني بصيغة ISO 8601 لوقت إنشاء الحدث
occurred_atstringوقت استلام البريد؛ اللحظة نفسها في data.email.received_at
schema_versionnumberإصدار مخطط الحمولة، 1؛ راجع الإصدارات
data.email.idstringالمعرّف العام للبريد الوارد
data.email.received_atstringوقت استلام البريد
data.email.size_bytesnumberحجم الرسالة الأصلية بالبايت
data.email.envelope.fromstringعنوان MAIL FROM عبر SMTP
data.email.envelope.toarrayعناوين RCPT TO عبر SMTP
data.email.headers.fromobject{ address, name } مُستخرج من ترويسة From
data.email.headers.to / .ccarrayمصفوفات من { address, name }
data.email.headers.subjectstringموضوع البريد
data.email.headers.message_idstring | nullترويسة Message-ID
data.email.headers.in_reply_tostring | nullترويسة In-Reply-To
data.email.headers.reply_tostring | nullترويسة Reply-To
data.email.headers.datestring | nullترويسة Date
data.email.body.text_previewstring | nullمعاينة نصية مختصرة
data.email.body.has_text / .has_htmlbooleanما إذا كان هناك جزء نصي أو HTML
data.email.attachmentsarrayانظر أدناه
data.email.authentication.spf/dkim/dmarc.resultstringنتيجة بأحرف صغيرة، مثل pass، fail، none
data.email.spam.scorenumberدرجة السبام
data.email.spam.is_spambooleanما إذا صُنِّف البريد كسبام
data.email.spam.reasonstring | nullسبب التصنيف، عند توفره
data.email.source.ip / .hostnamestring | nullعنوان IP واسم مضيف DNS العكسي للخادم المُرسِل
data.matched_routeobject | null{ id, name } للمسار المطابق، أو null

المرفقات

لا يُدرج محتوى المرفقات داخل الحمولة أبدًا. يحمل كل عنصر في data.email.attachments عنوان download_url:

الحقلالنوعالوصف
idstringمعرّف المرفق. ثابت: كل إعادة محاولة وكل إعادة تشغيل للحدث تحمل id نفسه للمرفق نفسه، فاستخدمه للتعرّف على مرفق نزّلته من قبل. يُنشأ download_url من جديد في كل محاولة
file_namestringاسم الملف الأصلي
content_typestringنوع MIME
size_bytesnumberالحجم بالبايت
is_inlinebooleanما إذا كان المرفق مُشارًا إليه ضمنيًا (مثل صورة مضمّنة)
content_idstring | nullContent-ID للمرفقات الضمنية
download_urlstring | nullعنوان URL موقّع مسبقًا، صالح لمدة 15 دقيقة من إنشاء الحمولة

لا يتطلب download_url ترويسة Authorization: العنوان نفسه هو بيانات الاعتماد، وتنتهي صلاحيته بعد 15 دقيقة من إنشاء حمولة ويب هوك. نزّل المرفقات فور استلام ويب هوك. إذا انتهت صلاحية العنوان، فاجلب الرسالة الأصلية كاملةً بدلاً من ذلك:

GET https://api.mail.land/v1/transactional/inbound/emails/{emailId}/raw

يتطلب هذا ترويسة Authorization المعتادة، ويُرجع ملف .eml الأصلي (Content-Type: message/rfc822): لا توجد نقطة نهاية لتنزيل كل مرفق على حدة.

التحقق من التوقيع

تُوقَّع ويب هوك البريد الوارد تمامًا كأي ويب هوك آخر في MailLand: نفس الترويسة، ونفس المخطط، ونفس معالجة السر. المرجع الأساسي، مع مثال محلول وأكواد تحقق بثماني لغات، هو Signature Verification؛ وكل ما ورد هناك ينطبق هنا دون تغيير.

باختصار:

X-MLND-Signature: t=1708171200,v1=<hex>
  • t هو طابع زمني بصيغة Unix بالثواني، وv1 هو HMAC-SHA256 بترميز سداسي عشري بأحرف صغيرة؛
  • السلسلة الموقَّعة هي {timestamp}.{raw_body}: الطابع الزمني، ثم نقطة حرفية، ثم النص كما وُرد تمامًا، قبل تحليل JSON؛
  • المفتاح هو سر توقيع ويب هوك، الذي يُعرض مرة واحدة عند الإنشاء؛
  • قارن بزمن ثابت وارفض أي طابع زمني أقدم من 300 ثانية.

لا يوجد مسار تسليم غير موقَّع: إذا لم يكن لدى ويب هوك سر قابل للاستخدام، يفشل MailLand التسليم بدلاً من إرساله بلا توقيع. ارفض أي طلب يصل بدون ترويسة X-MLND-Signature.

سلوك التسليم

رموز الاستجابة

يجب أن تستجيب نقطة النهاية الخاصة بك برمز حالة 2xx لتأكيد الاستلام. أي شيء آخر، حالة ليست 2xx أو انتهاء المهلة، يُحتسب كمحاولة تسليم فاشلة.

استجابتكإجراء MailLand
200–299تسليم ناجح، بلا إعادة محاولة
أي حالة أخرى، أو عدم الاستجابة خلال 15 ثانيةتسليم فاشل، تُعاد المحاولة (ضمن حد المحاولات أدناه)

سياسة إعادة المحاولة

تُعاد المحاولة الفاشلة وفق الجدول نفسه المطبّق على كل أحداث ويب هوك الأخرى، حتى 6 محاولات إجمالاً لكل حدث: المحاولة الأولى، ثم إعادة المحاولة فورًا، وبعد دقيقة، وبعد 5 دقائق، وبعد 15 دقيقة، وبعد ساعة. بعد المحاولة الفاشلة السادسة، يُعلَّم التسليم بأنه فشل نهائيًا. يمكنك إعادة تشغيله يدويًا. راجع منطق إعادة المحاولة للجدول الكامل.

نقطة النهاية المعطلة لأقل من ساعة ونصف تقريبًا تُعاد المحاولة عليها طوال تلك المدة، أما الانقطاع الأطول فينتهي بتسليم فاشل نهائيًا. صمّم نقطة نهايتك لتستجيب بسرعة حتى في حالة الخطأ، واعتمد على إعادة التشغيل اليدوية للانقطاعات الأطول.

كل محاولة تسليم فاشلة (لا كل حدث) تُحتسب ضمن إجمالي الإخفاقات المتتالية لويب هوك، ويُعطَّل ويب هوك تلقائيًا فور بلوغ هذا الإجمالي قيمة pauseThreshold الخاصة به (عند ضبطها على قيمة أكبر من 0)، وإلا فالقيمة الافتراضية البالغة 10 محاولات فاشلة متتالية: بعد ذلك، لا تُجدوَل أي رسائل بريد واردة جديدة لتسليمها عبر ويب هوك أصلًا، وينطبق هذا على نفس صف ويب هوك المستخدم لأحداث الرسائل أيضًا، لذا تتوقف تلك أيضًا. عند القيمة الافتراضية، تكفي رسالتان واردتان فشلتا بالكامل (6 محاولات لكل منهما) لتعطيله. أعد تفعيل ويب هوك من صفحة Webhooks، ثم أعد تشغيل الرسائل الفائتة.

إعادة تشغيل ويب هوك

إعادة تسليم ويب هوك لبريد وارد معيّن:

POST https://api.mail.land/v1/transactional/inbound/emails/{id}/replay-webhook
Replay webhook delivery
curl -X POST https://api.mail.land/v1/transactional/inbound/emails/k7m2x9q4p1w8z3/replay-webhook \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Replay response (202 Accepted)
{
  "emailId": "k7m2x9q4p1w8z3",
  "queued": true,
  "webhookUrl": "https://yourapp.com/webhooks/support",
  "message": "Webhook replay queued successfully"
}

يضع هذا الطلب تسليمًا دائمًا واحدًا في قائمة الانتظار إلى ويب هوك المسار المطابق، ويعيد 202 Accepted بعد تثبيت ذلك السجل؛ فهو لا يرسل بشكل متزامن. تحصل إعادة التشغيل على معرّف حدث جديد (event_id وX-MLND-Event-Id جديدان) وتحمل معرّف التسليم الأصلي في data.replay_of، فتتعامل إزالة التكرار لديك معها كحدث جديد مع بقاء إمكانية ربطها بالحدث الأصلي. يبقى data.idempotency_key كما في التسليم الأصلي. يحمل الطلب X-MLND-Source: inbound وX-MLND-Inbound-Email-Id، ويُوقَّع أي download_url للمرفقات في الحمولة وقت الإرسال، وتبدأ مدة صلاحيته (15 دقيقة) من ذلك الوقت لا من وقت استدعاء إعادة التشغيل.

إزالة الأحداث المكررة

تسليم ويب هوك الوارد مرة واحدة على الأقل (at-least-once): قد تؤدي إعادة المحاولة، أو انتهاء المهلة من جانبك بعد معالجة الطلب فعلًا، أو انتقال العمل بين العمال إلى تسليم الحدث نفسه أكثر من مرة. على المستقبِل إزالة التكرار.

  • يحمل كل ويب هوك وارد معرّف حدث ثابتًا في event_id وفي ترويسة X-MLND-Event-Id. وهو محدَّد لكل رسالة واردة ولكل ويب هوك، لذا تحمل كل محاولات تسليم الرسالة نفسها إلى الويب هوك نفسه المعرّف نفسه.
  • أزل التكرار بهذا المعرّف. احتفظ بالمعرّفات التي عالجتها وتجاهل أي معرّف سبق أن رأيته.
  • إعادة التشغيل اليدوي حدث جديد. له event_id جديد وحقل data.replay_of يحمل المعرّف الأصلي. إذا أردت تجاهل إعادة التشغيل عندما يكون الأصل قد عولج، فقارن data.replay_of أيضًا بالمعرّفات المخزّنة لديك.
Idempotent receiver (Node.js / Express)
app.post("/webhooks/support", express.raw({ type: "application/json" }), async (req, res) => {
  // 1. Verify X-MLND-Signature first (see Signature Verification).
  const eventId = req.header("X-MLND-Event-Id");
  const client = await db.connect();
  try {
    await client.query("BEGIN");
    // 2. Record the event id; the unique constraint makes this atomic.
    const inserted = await client.query(
      "INSERT INTO processed_events (event_id) VALUES ($1) ON CONFLICT (event_id) DO NOTHING",
      [eventId],
    );
    // 3. Only a first-time id enqueues work, in the SAME transaction (jobs table).
    if (inserted.rowCount > 0) {
      await client.query("INSERT INTO jobs (kind, payload) VALUES ($1, $2)", [
        "process-inbound-email",
        req.body.toString(),
      ]);
    }
    await client.query("COMMIT");
    res.sendStatus(200); // new or duplicate: acknowledge
  } catch (err) {
    await client.query("ROLLBACK"); // id and job are both undone, so the retry is not a "duplicate"
    res.sendStatus(500);
  } finally {
    client.release();
  }
});

يهمّ تسجيل المعرّف وإدراج العمل في قائمة الانتظار ضمن معاملة واحدة: فلو حُفظ المعرّف أولًا ثم فشل الإدراج، لتُجوهلت إعادة محاولة MailLand باعتبارها تكرارًا وضاعت الرسالة.

احتفظ بالمعرّفات المعالَجة مدة لا تقل عن أطول نافذة لإعادة المحاولة، إضافةً إلى أي إعادة تشغيل قد تطلقها.

ترويسات ويب هوك

يتضمن كل طلب ويب هوك الترويسات التالية:

الترويسةمثالالوصف
Content-Typeapplication/json
X-MLND-Signaturet=1708171200,v1=abc123...الطابع الزمني وتوقيع HMAC-SHA256
X-MLND-Eventinbound.receivedنوع الحدث
X-MLND-Event-Idevt_a1b2c3d4...معرّف الحدث الفريد
X-MLND-Event-Version1إصدار مخطط الحمولة
X-MLND-Webhook-Idw4k8m2q7p9x3z1المعرّف العام لويب هوك
X-MLND-Attempt1رقم محاولة التسليم (يبدأ من 1)
X-MLND-Sourceinboundالأنبوب الذي أنتج الحدث
X-MLND-Inbound-Email-Idk7m2x9q4p1w8z3البريد الوارد الذي يخص هذا التسليم

أفضل الممارسات

  • استجب بسرعة: أكّد استلام ويب هوك بـ200 فورًا، وعالج البريد بشكل غير متزامن في مهمة خلفية.
  • تحقّق من التوقيعات: تحقق دائمًا من ترويسة X-MLND-Signature لمنع الحمولات المزيفة.
  • تعامل مع التكرارات: التسليم مرة واحدة على الأقل، لذا أزل التكرار باستخدام X-MLND-Event-Id (وهو event_id في الحمولة). لإعادة التشغيل اليدوي معرّف جديد وقيمة data.replay_of هي المعرّف الأصلي؛ راجع إزالة الأحداث المكررة.
  • نزّل المرفقات فورًا: تنتهي صلاحية كل download_url بعد 15 دقيقة من إنشاء الحمولة؛ بعدها اجلب الرسالة الأصلية.
  • استخدم HTTPS: يجب أن تستخدم عناوين ويب هوك بروتوكول HTTPS في الإنتاج لتسليم آمن للحمولة.

استجابات الخطأ

الحالةالرمزالوصف
400INVALID_REQUESTلم يُطابَق أي مسار للبريد
400INVALID_REQUESTالمسار لا يملك ويب هوك مُهيّأ
400INVALID_REQUESTويب هوك المسار غير نشط، فعّله قبل إعادة التشغيل
404RESOURCE_NOT_FOUNDالبريد غير موجود لإعادة التشغيل

الخطوات التالية

  • Inbound Overview: تهيئة إعدادات البريد الوارد وعرض الرسائل
  • Inbound Routes: إعداد قواعد التوجيه لتسليم ويب هوك
  • Webhooks: تهيئة ويب هوك الصادر لأحداث البريد

في هذه الصفحة