Docs Get key
All server guides
Open Source Mail Servers

Receive with MailKite and reply through Postal

Receive parsed inbound messages with MailKite, then send replies through your Postal API without leaking credentials or duplicating sends.

Reviewed

Use MailKite for inbound email and Postal for outbound delivery when you want managed inbound parsing and webhooks but still operate Postal’s sending platform. Your app receives a signed MailKite event, verifies it, stores the work durably, and then calls Postal’s send API with a server-side credential. Postal remains responsible for outbound delivery and its operational requirements.

Configure both services

  1. Verify the receiving domain in MailKite, create the route, and configure your HTTPS webhook endpoint.
  2. Verify the sending domain in Postal and create a server API credential with only the access your deployment needs.
  3. Store the MailKite webhook signing secret and Postal API key in your deployment’s secret manager. Never send either credential to a browser or include it in logs.
  4. Verify the MailKite signature before reading the message as trusted input. Persist the event and its stable identity before acknowledging it; process the reply from a durable job.
  5. Define allowed From addresses and recipient policy in your app. A valid webhook signature proves delivery through MailKite, not that the email sender may authorize an outbound action.

See receive MailKite email as signed JSON for signature verification and Postal API sending for API credentials and response handling.

Send the reply with Postal

After applying your app’s sender and content policy, call Postal’s documented send endpoint from a server-side worker. This minimal example sends a new message; it deliberately does not invent reply-header or threading behavior. Use Postal’s current API reference for supported headers and thread handling if you need an in-thread reply.

const response = await fetch(`${process.env.POSTAL_URL}/api/v1/send/message`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Server-API-Key": process.env.POSTAL_API_KEY,
  },
  body: JSON.stringify({
    to: [approvedRecipient],
    from: "agent@your-sending-domain.example",
    subject: `Re: ${originalSubject}`,
    plain_body: replyText,
  }),
});

const result = await response.json();
if (!response.ok || result.status !== "success") {
  // Do not put the full response (which can contain addresses/content) in logs.
  throw new Error("Postal send failed; inspect the protected send-attempt record");
}

Replace the example sender with a verified Postal sending address and derive the recipient from the verified inbound event under your application’s policy. Postal’s API may return success in the JSON envelope; check both that status and the HTTP response. Record returned message IDs and correlate later delivery/bounce webhooks with your durable job.

Make retries safe

Do not blindly retry an outbound request after a timeout: Postal may have accepted the message even if your app did not receive the response. Store a send-attempt record before calling Postal, retain the outcome and returned IDs, and reconcile ambiguous outcomes against Postal’s message history or webhooks before resending. Make event handling and outbound actions idempotent independently.

MailKite’s inbound attachment references and normalized auth metadata are not Postal API fields. If your reply includes an attachment, retrieve it according to MailKite’s current receiving contract, enforce your own size/type limits, and encode it using the format supported by Postal’s current send API. Do not forward untrusted attachments automatically.

This pattern keeps the two providers’ responsibilities explicit: MailKite receives and reports inbound messages; Postal sends outbound messages. It does not make MailKite a Postal-compatible API client or transfer Postal’s deliverability operations to MailKite.