MailKite
Get your API key
All server guides
Open Source Mail Servers

Receive inbound email with Postal HTTP routes

Route inbound Postal messages to an HTTPS endpoint, acknowledge requests safely, and handle retries, parsed payloads, and attachments.

Reviewed

Postal receives inbound email for domains whose MX records point to your Postal installation. To pass a message to an application, create an incoming route with an HTTP endpoint, select JSON and either the processed or raw payload, and configure your endpoint to return 200 OK within five seconds. Postal retries non-5xx failures up to 18 times with exponential backoff; a 5xx response fails immediately and can cause a bounce.

Prerequisites

Postal logo

You need a running Postal server, a domain configured for receiving, MX records pointing to the Postal host, and an HTTPS endpoint reachable from that host. Review Postal’s incoming email documentation and incoming routing feature because route names and controls are managed in your Postal installation.

Postal’s route can send form data or JSON and can send the original message or a processed dictionary. For an application integration, processed JSON is usually easier to consume. The sample below assumes the route is configured to send JSON with a processed payload.

Create an incoming route

In the Postal web interface:

  1. Open the mail server and create or select the incoming domain/address.
  2. Add an incoming route that matches the recipient address or pattern you intend to handle.
  3. Add an HTTP endpoint using an HTTPS URL controlled by your application.
  4. Select JSON request encoding and the processed message payload. Enable attachment data only if the application needs it.
  5. Send a test message and inspect the route attempt in Postal’s message view.

The exact menu labels can change between Postal releases. Confirm the route’s matching conditions so that the intended recipient is selected and unrelated addresses are not routed to the same application.

Acknowledge first, process durably

Postal expects the HTTP endpoint to respond within five seconds. Do not run slow business logic, call an LLM, or synchronously write large attachments before acknowledging. Validate the request, persist a durable job, then return success. A worker can process the stored job after the HTTP response.

Example Express handler outline:

import express from "express";

const app = express();
app.use(express.json({ limit: "15mb" }));

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 {
    // Insert with a unique key on Postal's message ID so retries are idempotent.
    await saveIncomingMessageOnce({
      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,
      attachments: message.attachments ?? [],
    });
    return res.sendStatus(200);
  } catch (error) {
    // Temporary persistence failure: allow Postal's documented retry behavior.
    console.error("Could not persist Postal message", error);
    return res.sendStatus(503);
  }
});

app.listen(3000);

saveIncomingMessageOnce is an application function: implement it with a durable database transaction and a unique constraint on (provider, providerMessageId). A duplicate request should find the existing job and still return 200; it should not enqueue the same downstream action twice. The sample deliberately avoids responding 500 for malformed payloads because Postal treats 5xx as an immediate permanent failure. Alert on invalid requests and investigate them rather than retrying them indefinitely.

Handle processed payloads and attachments

Postal’s processed payload can include sender and recipient fields, plain_body, html_body, message identifiers, spam_status, and optionally an attachments array. Attachment data is Base64-encoded. Decode it only after enforcing size limits, and store it outside the application database when appropriate. Do not trust filenames or MIME types as proof of file safety.

The payload’s spam_status is not a structured SPF/DKIM/DMARC verdict. If an application needs aligned sender-authentication results, retain or parse the relevant headers using a carefully tested mail parser; do not treat a “not spam” result as proof that the sender is authentic. Inbound email content is untrusted input, especially when it is passed to an AI agent or used to trigger application actions.

Test retries and failure behavior

Use a controlled mailbox and verify all of these cases:

  • A valid request is persisted before 200 OK is returned.
  • A repeat delivery of the same message ID does not duplicate business work.
  • A temporary database outage produces a retryable non-5xx response only if that matches the behavior documented for your current Postal version. Postal’s documentation says non-5xx failures are retried; use an intentional status and verify it in your environment.
  • A 5xx response is treated as an immediate failure and may bounce, so never use it as a generic “try later” response without understanding that consequence.
  • Requests slower than five seconds are avoided; processing continues from the durable job after acknowledgment.
  • Attachments have bounded request/body size and safe storage/inspection rules.

Postal’s current HTTP receiving guide documents up to 18 attempts with exponential backoff for retryable failures. Keep your own logs keyed by the Postal message ID and inspect the attempt history in Postal when diagnosing delivery.

Compare the MailKite receiving path

Postal routes incoming messages to an endpoint you operate and returns its provider-specific processed payload. MailKite delivers parsed inbound messages as signed JSON webhooks, with a documented event contract, signature verification, and attachment URLs. That can remove the need to operate an inbound Postal server when the application does not need Postal’s self-hosted delivery platform.

Use the MailKite receiving guide and receiving documentation for the current setup. For an agent inbox comparison, see Postal for AI agents. These products have different payloads, authentication, operating responsibilities, and pricing; changing the webhook URL alone is not a complete migration.

Sources