MailLandDocs
ایمیل ورودی

وب‌هوک‌های ورودی

دریافت داده‌های ایمیل ورودی پردازش‌شده از طریق اعلان‌های وب‌هوک

هنگامی که یک ایمیل ورودی با مسیری تطبیق داشته باشد که در actions آن webhook وجود دارد، MailLand ایمیل پردازش‌شده را به‌صورت یک درخواست POST با فرمت JSON به وب‌هوک همان مسیر ارسال می‌کند. این صفحه قالب بار، تأیید امضا و رفتار تحویل را پوشش می‌دهد.

بار وب‌هوک

MailLand یک درخواست POST با بدنهٔ 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 و مانند آن‌ها) به‌جای ارسال به‌صورت null، کاملاً از JSON حذف می‌شوند. نمونهٔ بالا برای خوانایی null را نشان می‌دهد؛ روی حضور کلید حساب نکنید.

فیلدهای بار

فیلدنوعتوضیحات
eventstringهمیشه inbound.received
event_idstringشناسهٔ رویداد، برابر با هدر X-MLND-Event-Id. این شناسه برای هر پیام ورودی و هر وب‌هوک ثابت است (evt_ و پس از آن ۳۲ نویسهٔ هگزادسیمال)؛ هر تلاش مجدد و انتشار دوبارهٔ همان تحویل، 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آی‌پی و نام میزبان 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نشانی از پیش امضاشده، معتبر برای ۱۵ دقیقه از زمان تولید بار

download_url نیازی به سرصفحهٔ Authorization ندارد: خود نشانی، اعتبارنامه است و ۱۵ دقیقه پس از تولید بار وب‌هوک منقضی می‌شود. بلافاصله پس از دریافت وب‌هوک، پیوست‌ها را دانلود کنید. اگر نشانی منقضی شده، به‌جای آن کل پیام خام را دریافت کنید:

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 یک زمان یونیکس بر حسب ثانیه است و v1 یک HMAC-SHA256 به‌صورت هگزادسیمال با حروف کوچک است؛
  • رشتهٔ امضاشده به‌صورت {timestamp}.{raw_body} است: زمان، سپس یک نقطهٔ واقعی، و سپس بدنه دقیقاً همان‌طور که دریافت شده، پیش از تجزیهٔ JSON؛
  • کلید، رازِ امضای وب‌هوک است که فقط یک‌بار هنگام ایجاد نمایش داده می‌شود؛
  • مقایسه را در زمان ثابت انجام دهید و زمانی قدیمی‌تر از ۳۰۰ ثانیه را رد کنید.

هیچ مسیر تحویل بدون امضایی وجود ندارد: اگر وب‌هوکی رازِ قابل‌استفاده نداشته باشد، MailLand به‌جای ارسال بدون امضا، تحویل را ناموفق می‌کند. هر درخواستی که بدون سرصفحهٔ X-MLND-Signature برسد را رد کنید.

رفتار تحویل

کدهای پاسخ

نقطهٔ پایانی شما باید با یک کد وضعیت 2xx برای تأیید دریافت پاسخ دهد. هر چیز دیگری، وضعیتی غیر از 2xx یا اتمام مهلت، به‌عنوان یک تلاش تحویل ناموفق شمرده می‌شود.

پاسخ شمااقدام MailLand
200–299تحویل موفق، بدون تلاش مجدد
هر وضعیت دیگر، یا نبود پاسخ در ۱۵ ثانیهتحویل ناموفق، تلاش مجدد می‌شود (مشروط به سقف تلاش زیر)

سیاست تلاش مجدد

یک تلاش ناموفق با همان زمان‌بندی سایر رویدادهای وب‌هوک دوباره انجام می‌شود، تا سقف ۶ تلاش در مجموع برای هر رویداد: تلاش اول، سپس تلاش مجدد بلافاصله، بعد از ۱ دقیقه، ۵ دقیقه، ۱۵ دقیقه و ۱ ساعت. پس از ششمین تلاش ناموفق، تحویل به‌عنوان ناموفق دائمی علامت‌گذاری می‌شود. می‌توانید آن را به‌صورت دستی بازپخش کنید. برای زمان‌بندی کامل به منطق تلاش مجدد نگاه کنید.

نقطهٔ پایانی‌ای که کمتر از حدود یک ساعت و نیم از دسترس خارج باشد در کل همین بازه دوباره امتحان می‌شود، اما قطعی طولانی‌تر به تحویل ناموفق دائمی ختم می‌شود. نقطهٔ پایانی خود را طوری طراحی کنید که حتی در حالت خطا هم سریع پاسخ دهد و برای قطعی‌های طولانی‌تر به بازپخش دستی تکیه کنید.

هر تلاش تحویل ناموفق (نه هر رویداد) در مجموع شکست‌های متوالی وب‌هوک محاسبه می‌شود، و وب‌هوک به‌محض رسیدن این مجموع به pauseThreshold خودش (وقتی روی مقداری بزرگ‌تر از ۰ تنظیم شده باشد)، وگرنه مقدار پیش‌فرض یعنی ده شکست پیاپی، به‌طور خودکار غیرفعال می‌شود: پس از آن، ایمیل‌های ورودی جدید هیچ تحویل وب‌هوکی برای آن در صف قرار نمی‌دهند، و چون همان ردیف وب‌هوک برای رویدادهای پیام هم استفاده می‌شود، آن‌ها هم متوقف می‌شوند. با مقدار پیش‌فرض، دو ایمیل ورودی که کاملاً شکست بخورند (هرکدام ۶ تلاش) برای غیرفعال‌شدن کافی است. وب‌هوک را در صفحهٔ 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 پیوست در محموله در لحظهٔ ارسال امضا می‌شود؛ اعتبار آن ۱۵ دقیقه از همان لحظه است، نه از لحظهٔ فراخوانی بازپخش.

حذف رویدادهای تکراری

تحویل وب‌هوک ورودی حداقل یک‌بار (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شمارهٔ تلاش تحویل (از ۱ شروع می‌شود)
X-MLND-Sourceinboundخطی که رویداد را تولید کرده
X-MLND-Inbound-Email-Idk7m2x9q4p1w8z3ایمیل ورودی مربوط به این تحویل

بهترین شیوه‌ها

  • سریع پاسخ دهید: وب‌هوک را بلافاصله با 200 تأیید کنید و ایمیل را به‌صورت ناهم‌زمان در یک کار پس‌زمینه پردازش کنید.
  • امضاها را تأیید کنید: همیشه سرصفحهٔ X-MLND-Signature را برای جلوگیری از بارهای جعلی اعتبارسنجی کنید.
  • موارد تکراری را مدیریت کنید: تحویل حداقل یک‌بار است؛ پس بر اساس X-MLND-Event-Id (همان event_id محموله) تکراری‌ها را حذف کنید. بازپخش دستی شناسهٔ جدید دارد و data.replay_of آن شناسهٔ اصلی است؛ بخش حذف رویدادهای تکراری را ببینید.
  • پیوست‌ها را فوراً دانلود کنید: هر download_url ۱۵ دقیقه پس از تولید بار منقضی می‌شود؛ پس از آن پیام خام را دریافت کنید.
  • از HTTPS استفاده کنید: آدرس‌های وب‌هوک باید در محیط تولید از HTTPS برای تحویل امن بار استفاده کنند.

پاسخ‌های خطا

وضعیتکدتوضیحات
400INVALID_REQUESTهیچ مسیری برای این ایمیل منطبق نشد
400INVALID_REQUESTمسیر وب‌هوکی پیکربندی‌شده ندارد
400INVALID_REQUESTوب‌هوک مسیر غیرفعال است؛ پیش از بازپخش آن را فعال کنید
404RESOURCE_NOT_FOUNDایمیل موردنظر برای بازپخش یافت نشد

گام‌های بعدی

  • Inbound Overview: پیکربندی تنظیمات ورودی و مشاهدهٔ ایمیل‌ها
  • Inbound Routes: تنظیم قوانین مسیریابی برای تحویل وب‌هوک
  • Webhooks: پیکربندی وب‌هوک‌های خروجی برای رویدادهای ایمیل

در این صفحه