Docs Get key
All server guides
Open Source Mail Servers

Connect Postal inbound routes to a MailKite app

Adapt Postal's incoming HTTP payload for an app that also uses MailKite, without confusing provider payloads or webhook signatures.

Reviewed

You can keep Postal as the receiving server and deliver its incoming-route HTTP request to an application that also uses MailKite’s SDK or agent workflow. In this setup Postal remains responsible for receiving the message; MailKite does not authenticate or transform Postal’s callback automatically. Your adapter must validate the Postal route request, map the provider payload, and apply your app’s own processing policy.

Configure a Postal route

In Postal, create an incoming route for the intended address and configure an HTTPS endpoint with JSON encoding and the processed payload. Enable attachment data only if you need it. Postal expects a response within five seconds; persist a durable job before returning 200 OK. Its documented behavior retries failures, but 5xx fails immediately and may bounce the message. See Postal’s HTTP payload documentation and the Postal inbound guide.

Adapt the payload at your boundary

Postal’s processed payload can include id, rcpt_to, mail_from, plain_body, html_body, spam_status, and optional attachments. Attachment data is Base64 encoded. Map only fields that are present and needed into your app’s internal message type; preserve Postal’s message ID for idempotency.

Do not treat spam_status: "NotSpam" as an SPF/DKIM/DMARC pass. It is a spam classification, not a structured authentication verdict. If your policy needs sender authentication, use results produced by your trusted receiving infrastructure and account for alignment. A MIME parser can read an Authentication-Results header, but cannot establish that it is genuine: senders can supply forged headers. Trust only results from a configured authentication service at your receiving boundary; if you cannot establish that provenance, treat authentication as unknown. A Postal payload does not become a MailKite-signed webhook by being forwarded to a MailKite app; do not call MailKite.verifyWebhook on it or fabricate MailKite’s auth field.

Likewise, enforce request limits before decoding Base64 attachments, apply file safety checks, and avoid passing untrusted HTML or attachment content directly to an agent as instructions. If you want to use a MailKite agent route, call it only through a documented MailKite interface that accepts your application’s data; otherwise run your own agent from a durable queue.

Minimal adapter outline

app.post("/postal/incoming", async (req, res) => {
  const message = req.body;
  if (!message || typeof message.id !== "number" || !message.rcpt_to) {
    return res.sendStatus(400);
  }

  try {
    // Store once under a unique (provider, providerMessageId) key.
    await enqueueOnce({
      provider: "postal",
      providerMessageId: String(message.id),
      recipient: message.rcpt_to,
      sender: message.mail_from,
      subject: message.subject,
      text: message.plain_body,
      html: message.html_body,
      spamStatus: message.spam_status,
      attachments: message.attachments ?? [],
    });
    return res.sendStatus(200);
  } catch (error) {
    console.error("Could not persist Postal message", error);
    // Postal retries non-200, non-5xx responses; 5xx permanently fails delivery.
    return res.sendStatus(429);
  }
});

This outline is not a standalone server: app is your configured Express app and enqueueOnce must be a durable, atomic insert with a unique (provider, providerMessageId) constraint. Scope that key to the Postal server too if multiple servers use the endpoint. Configure a JSON body-size limit before the handler. A queue failure returns 429 so Postal can retry; reserve 5xx for a deliberate permanent failure, not temporary storage trouble.

Apply request authenticity controls before this handler. Do not assume a secret header exists unless you configured and verified it in your Postal deployment; use network controls or a dedicated authenticated proxy where needed. The durable queue should deduplicate repeated callbacks so a retry cannot run the same agent action twice.

For a complete MailKite webhook receiver, use MailKite’s signed receiving guide. For a working side-by-side Postal and MailKite agent example, see the Postal AI agent demo.