MailLandDocs
Gelen E-posta

Gelen Posta Webhook'ları

Webhook bildirimleri aracılığıyla ayrıştırılmış gelen e-posta verilerini alın

Gelen bir e-posta, actions alanında webhook bulunan bir rotayla eşleştiğinde, MailLand ayrıştırılmış e-postayı o rotanın webhook'una bir JSON POST isteği olarak gönderir. Bu sayfa yük biçimini, imza doğrulamasını ve teslimat davranışını kapsar.

Webhook Yükü

MailLand, webhook URL'nize aşağıdaki JSON gövdesiyle bir POST isteği gönderir:

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"
    }
  }
}

E-postanın tam gövdesi hiçbir zaman satır içinde yer almaz: yalnızca kısaltılmış bir text_preview ile has_text/has_html bayrakları bulunur. body.html alanı yoktur.

Değeri olmayan alanlar (name, in_reply_to, content_id, reason, data.matched_route vb.) null olarak gönderilmek yerine JSON'dan tamamen çıkarılır. Yukarıdaki örnek okunabilirlik için null gösterir; anahtarın var olacağına güvenmeyin.

Yük Alanları

AlanTürAçıklama
eventstringHer zaman inbound.received
event_idstringOlay kimliği; X-MLND-Event-Id başlığına eşittir. Gelen ileti ve webhook başına sabittir (evt_ ve ardından 32 onaltılık karakter): aynı teslimatın her yeniden denemesi ve yeniden yayımı aynı event_id, başlık ve gövdeyi taşır. Elle yeniden oynatma yeni bir event_id taşır ve data.replay_of alanını doldurur.
data.idempotency_keystringBir rota eşleştiğinde {emailPublicId}_{routePublicId}, eşleşmediğinde (veya rota artık mevcut değilse) yalnızca {emailPublicId}. Yeniden denemeler ve manuel tekrarlar boyunca sabittir
data.replay_ofstringYalnızca elle yeniden oynatmada bulunur: özgün teslimatın taşıdığı event_id. Normal teslimatlarda ve olay kimlikleri sabit hale gelmeden önce alınmış iletilerin yeniden oynatılmasında gönderilmez
timestampstringOlayın oluşturulduğu ISO 8601 zaman damgası
occurred_atstringE-postanın alındığı an; data.email.received_at ile aynı an
schema_versionnumberYük şema sürümü, 1; bkz. Sürümleme
data.email.idstringGelen e-postanın genel kimliği
data.email.received_atstringE-postanın alındığı zaman
data.email.size_bytesnumberHam mesaj boyutu (bayt)
data.email.envelope.fromstringSMTP MAIL FROM adresi
data.email.envelope.toarraySMTP RCPT TO adresleri
data.email.headers.fromobjectFrom başlığından ayrıştırılan { address, name }
data.email.headers.to / .ccarray{ address, name } dizileri
data.email.headers.subjectstringE-posta konusu
data.email.headers.message_idstring | nullMessage-ID başlığı
data.email.headers.in_reply_tostring | nullIn-Reply-To başlığı
data.email.headers.reply_tostring | nullReply-To başlığı
data.email.headers.datestring | nullDate başlığı
data.email.body.text_previewstring | nullKısaltılmış düz metin önizlemesi
data.email.body.has_text / .has_htmlbooleanMetin veya HTML bölümünün olup olmadığı
data.email.attachmentsarrayAşağıya bakın
data.email.authentication.spf/dkim/dmarc.resultstringKüçük harfli sonuç, ör. pass, fail, none
data.email.spam.scorenumberSpam puanı
data.email.spam.is_spambooleanE-postanın spam olarak sınıflandırılıp sınıflandırılmadığı
data.email.spam.reasonstring | nullMevcutsa sınıflandırma nedeni
data.email.source.ip / .hostnamestring | nullGönderen sunucunun IP'si ve ters DNS ana bilgisayar adı
data.matched_routeobject | nullEşleşen rotanın { id, name } bilgisi veya null

Ekler

Ek içeriği hiçbir zaman satır içinde yer almaz. data.email.attachments içindeki her giriş bir download_url taşır:

AlanTürAçıklama
idstringEk kimliği. Kararlıdır: olayın her yeniden denemesi ve her yeniden oynatması aynı ek için aynı id değerini taşır; daha önce indirdiğiniz bir eki tanımak için bunu kullanın. download_url her denemede yeniden üretilir
file_namestringOrijinal dosya adı
content_typestringMIME türü
size_bytesnumberBayt cinsinden boyut
is_inlinebooleanEkin satır içinde referans verilip verilmediği (ör. gömülü bir görsel)
content_idstring | nullSatır içi ekler için Content-ID
download_urlstring | nullÖn imzalı URL; yükün oluşturulmasından itibaren 15 dakika geçerlidir

download_url, Authorization başlığı gerektirmez: URL'nin kendisi kimlik bilgisidir ve webhook yükü oluşturulduktan 15 dakika sonra sona erer. Webhook'u aldığınızda ekleri hemen indirin. URL'nin süresi dolmuşsa, bunun yerine ham mesajın tamamını alın:

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

Bu, normal Authorization başlığınızı gerektirir ve orijinal .eml dosyasını döndürür (Content-Type: message/rfc822): ek başına indirme uç noktası yoktur.

İmza Doğrulama

Gelen webhook'lar diğer her MailLand webhook'u ile tamamen aynı şekilde imzalanır: aynı başlık, aynı şema, aynı sır işleme. Sekiz dilde işlenmiş bir örnek ve doğrulayıcı kod içeren kurallı referans Signature Verification sayfasıdır; orada yazılan her şey burada değişmeden geçerlidir.

Kısaca:

X-MLND-Signature: t=1708171200,v1=<hex>
  • t saniye cinsinden bir Unix zaman damgasıdır, v1 küçük harfli onaltılık bir HMAC-SHA256'dır;
  • imzalanan dize {timestamp}.{raw_body} şeklindedir: zaman damgası, gerçek bir nokta, ardından JSON ayrıştırmasından önce, alındığı haliyle gövde;
  • anahtar, webhook oluşturulduğunda bir kez gösterilen imzalama sırrıdır;
  • sabit zamanda karşılaştırın ve 300 saniyeden eski bir zaman damgasını reddedin.

İmzasız bir teslimat yolu yoktur: bir webhook'un kullanılabilir bir sırrı yoksa, MailLand onu imzasız göndermek yerine teslimatı başarısız kılar. X-MLND-Signature başlığı olmadan gelen her isteği reddedin.

Teslimat Davranışı

Yanıt Kodları

Uç noktanız, alındığını onaylamak için bir 2xx durum koduyla yanıt vermelidir. Bunun dışındaki her şey, 2xx olmayan bir durum veya zaman aşımı, başarısız bir teslimat girişimi olarak sayılır.

YanıtınızMailLand Eylemi
200–299Teslimat başarılı, yeniden deneme yok
Başka herhangi bir durum, veya 15 saniye içinde yanıt yoksaTeslimat başarısız, yeniden denenir (aşağıdaki deneme sınırına tabi)

Yeniden Deneme İlkesi

Başarısız bir deneme, diğer tüm webhook olaylarıyla aynı takvimle yeniden denenir, olay başına toplam 6 denemeye kadar: ilk deneme, ardından hemen, 1 dakika, 5 dakika, 15 dakika ve 1 saat sonra yeniden denemeler. 6. başarısız denemeden sonra, teslimat kalıcı olarak başarısız olarak işaretlenir. Bunu manuel olarak yeniden oynatabilirsiniz. Tam takvim için Yeniden Deneme Mantığı sayfasına bakın.

Yaklaşık bir buçuk saatten kısa süre kapalı kalan bir uç nokta bu pencere boyunca yeniden denenir, daha uzun bir kesinti ise kalıcı olarak başarısız bir teslimatla sonuçlanır. Uç noktanızı hatada bile hızlı yanıt verecek şekilde tasarlayın ve daha uzun kesintiler için manuel tekrara güvenin.

Her başarısız teslimat denemesi (her olay değil) webhook'un ardışık başarısızlık toplamına sayılır ve bu toplam webhook'un kendi pauseThreshold değerine ulaştığında (0'dan büyük bir değere ayarlanmışsa) ya da aksi halde varsayılan olan 10 ardışık başarısızlıkta webhook otomatik olarak devre dışı bırakılır: bundan sonra yeni gelen e-postalar onun için hiçbir webhook teslimatı kuyruğa almaz; bu aynı webhook satırı mesaj olayları için de kullanıldığından, onlar da durur. Varsayılan eşikte, tamamen başarısız olan iki gelen e-posta (her biri 6 deneme) webhook'u devre dışı bırakmaya yeter. Webhook'u Webhooks sayfasından yeniden etkinleştirin, ardından kaçırılan e-postaları yeniden oynatın.

Bir Webhook'u Yeniden Oynatma

Belirli bir gelen e-posta için webhook'u yeniden teslim edin:

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"
}

Bu işlem, eşleşen rotanın webhook'una kalıcı bir teslimatı kuyruğa alır ve ilgili satır commit edildiğinde 202 Accepted döndürür; eşzamanlı gönderim yapmaz. Yeniden oynatma yeni bir olay kimliği alır (yeni bir event_id ve X-MLND-Event-Id) ve özgün teslimatın kimliğini data.replay_of içinde taşır; böylece tekrar eleme mekanizmanız onu yeni bir olay olarak görür, yine de özgün olayla ilişkilendirebilirsiniz. data.idempotency_key özgün teslimattakiyle aynı kalır. İstek X-MLND-Source: inbound ve X-MLND-Inbound-Email-Id başlıklarını taşır; yükteki her ek download_url gönderim anında imzalanır ve o andan itibaren 15 dakika geçerlidir, yeniden oynatmayı çağırdığınız andan itibaren değil.

Olayları tekrarlardan arındırma

Gelen webhook teslimatı en az bir kez (at-least-once) yapılır: yeniden deneme, isteği zaten işledikten sonra sizin tarafınızda oluşan zaman aşımı veya worker devri aynı olayı birden fazla kez teslim edebilir. Alıcılar tekrarları elemelidir.

  • Her gelen webhook sabit bir olay kimliği taşır; bu kimlik event_id alanında ve X-MLND-Event-Id başlığındadır. Gelen ileti ve webhook başına belirleyicidir; yani aynı iletinin aynı webhook'a yapılan tüm teslimat denemeleri aynı kimliği taşır.
  • Bu kimliğe göre tekrarları eleyin. İşlediğiniz kimlikleri saklayın ve daha önce gördüğünüz kimliği atlayın.
  • Elle yeniden oynatma yeni bir olaydır. Yeni bir event_id ve özgün kimliği tutan bir data.replay_of alanı vardır. Özgün olay zaten işlendiyse yeniden oynatmanın yok sayılmasını istiyorsanız data.replay_of değerini de sakladığınız kimliklerle karşılaştırın.
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();
  }
});

Kimliği kaydetmek ve işi kuyruğa almak tek bir işlemde yapılmalıdır: kimlik önce kaydedilir ve kuyruğa alma başarısız olursa, MailLand'ın yeniden denemesi tekrar sayılıp atlanır ve e-posta kaybolur.

İşlenen kimlikleri en az en uzun yeniden deneme pencerenizin süresi kadar, ayrıca tetikleyebileceğiniz her yeniden oynatmayı da kapsayacak şekilde saklayın.

Webhook Başlıkları

Her webhook isteği şu başlıkları içerir:

BaşlıkÖrnekAçıklama
Content-Typeapplication/json
X-MLND-Signaturet=1708171200,v1=abc123...Zaman damgası ve HMAC-SHA256 imzası
X-MLND-Eventinbound.receivedOlay türü
X-MLND-Event-Idevt_a1b2c3d4...Benzersiz olay kimliği
X-MLND-Event-Version1Yük şema sürümü
X-MLND-Webhook-Idw4k8m2q7p9x3z1Webhook'un genel kimliği
X-MLND-Attempt1Teslimat deneme numarası (1'den başlar)
X-MLND-SourceinboundOlayı hangi hattın ürettiği
X-MLND-Inbound-Email-Idk7m2x9q4p1w8z3Bu teslimatın ilgili olduğu gelen e-posta

En İyi Uygulamalar

  • Hızlı yanıt verin: webhook'u hemen 200 ile onaylayın ve e-postayı arka planda bir işte asenkron olarak işleyin.
  • İmzaları doğrulayın: sahte yükleri önlemek için her zaman X-MLND-Signature başlığını doğrulayın.
  • Tekrarları yönetin: teslimat en az bir kez yapılır; bu yüzden X-MLND-Event-Id (yükteki event_id) ile tekrarları eleyin. Elle yeniden oynatmanın kimliği yenidir ve data.replay_of özgün kimliği gösterir; bkz. Olayları tekrarlardan arındırma.
  • Ekleri hemen indirin: her download_url, yük oluşturulduktan 15 dakika sonra sona erer; bundan sonra ham mesajı alın.
  • HTTPS kullanın: webhook URL'leri, güvenli yük teslimatı için üretimde HTTPS kullanmalıdır.

Hata Yanıtları

DurumKodAçıklama
400INVALID_REQUESTE-posta için eşleşen bir rota yok
400INVALID_REQUESTRotanın yapılandırılmış bir webhook'u yok
400INVALID_REQUESTRotanın webhook'u devre dışı: yeniden oynatmadan önce etkinleştirin
404RESOURCE_NOT_FOUNDYeniden oynatma için e-posta bulunamadı

Sonraki Adımlar

  • Inbound Overview: gelen posta ayarlarını yapılandırma ve e-postaları görüntüleme
  • Inbound Routes: webhook teslimatı için yönlendirme kurallarını ayarlama
  • Webhooks: e-posta olayları için giden webhook yapılandırması

Bu sayfada