Skip to content
Docs menuCurrent page: Receiving

Guides

Receiving

Turn receiving on for a verified domain and mail to any address on it arrives in its project. Fetch the body, attachments and original message with a full-access live key.

Tested with resend@6.30.0

Last reviewed

On this page (7)

How catch-all routing works

Receiving is optional, and off for a new domain. Once you turn it on and the domain is verified, Letterpier’s mail server accepts mail for any address on it: support@, reply+4711@, anything. Each message goes to the project that owns the domain and appears there as Received.

  • Routing uses the SMTP envelope recipient (RCPT TO), not the visible To header, which the sender can set to anything.
  • The body, the original message (.eml) and every attachment are stored encrypted at rest. This is encryption at rest with keys the application manages. It is not end-to-end encryption. Subjects, addresses and event metadata stay in plaintext so they can be searched, and Postal, our mail server, keeps the raw message in plaintext in its private database for 7 days.
  • Received messages can be up to about 14 MB, including attachments; larger messages are refused and not stored.
  • Deliveries from the mail server are deduplicated, so a retried hand-off never creates a second message.
  • When incoming mail fails, no failure bounce goes back to the sender, so forged senders don’t receive backscatter.

Setting up MX

Receiving needs one MX record at the domain’s name. It is only required while receiving is on for the domain, and it never holds up sending: a sending-only domain doesn’t publish it. Turn receiving on when you add the domain, or later on the domain’s page in the dashboard; through the API, pass receiving: true when you create the domain. Receiving shows as enabled once the domain is verified and the MX record matches.

The receiving record
TypeNameValuePriority
MXnotify.yourproduct.examplemail.letterpier.com10

The receiving record

  • Type
    MX
    Name
    notify.yourproduct.example
    Value
    mail.letterpier.com
    Priority
    10
Use a dedicated subdomain

Turning receiving on and off

The email.received event

When a message arrives, subscribed endpoints receive a signed email.received event. It carries the message ID and enough to decide what to do next; fetch the content with the ID.

email.received
{  "type": "email.received",  "created_at": "2026-09-29T09:14:05.118Z",  "data": {    "email_id": "0f6d2c1e-5a7b-4c0e-9d7e-3b1f8a2c4d60",    "created_at": "2026-09-29T09:14:04.902Z",    "from": "Ada Lovelace <ada@customer.example>",    "to": ["support@notify.yourproduct.example"],    "subject": "Question about my invoice",    "sandbox": false,    "attachments": [      {        "id": "7c2e9b14-0d3a-4f6b-8e21-5a9c0f1d2b73",        "filename": "screenshot.png",        "content_type": "image/png"      }    ],    "message_id": "<CAF=abc123@mail.customer.example>"  }}

to holds the envelope recipient. attachments lists the files without their content. message_id is the sender’s Message-ID header, when there is one.

Verifying and deduplicating webhooks

Retrieving a message

Fetch a received message with mail.emails.receiving.get(id) (GET /emails/receiving/:id) and a full-access live key. Sandbox keys and sending-only keys are refused with 403 restricted_api_key.

receive.ts
const { data: email, error } = await mail.emails.receiving.get(emailId);email?.subject; // As receivedemail?.text; // Plain-text body, or nullemail?.html; // HTML body, or nullemail?.raw; // Letterpier extension: a signed link to the original .eml

The response has the sender, the envelope recipient, the subject, both bodies, the attachments and raw: a signed link to the original .eml, byte for byte as it arrived. Like every download link, it expires after 15 minutes.

Differs from Resend

Attachments

mail.emails.receiving.attachments.list({ emailId }) returns every attachment’s metadata; .get({ emailId, id }) returns one. Each has an id, filename, size in bytes, content_type, content_id, and a signed download_url with its expires_at.

attachments.ts
const { data: list } =  await mail.emails.receiving.attachments.list({ emailId });for (const attachment of list?.data ?? []) {  // Links expire after 15 minutes. Ask again for a fresh one.  const { data: fresh } = await mail.emails.receiving.attachments.get({    emailId,    id: attachment.id,  });  const response = await fetch(fresh!.download_url);  const bytes = new Uint8Array(await response.arrayBuffer());  console.log(attachment.filename, bytes.byteLength);}
  • Download links expire after 15 minutes. Request the metadata again for a new link.
  • Downloads arrive as a file (Content-Disposition: attachment), never rendered in a browser.
  • Links stop working once the message is deleted, by you or by retention.

Listing received mail

mail.emails.receiving.list() (GET /emails/receiving) returns received messages, newest first, with the same full-access live key.

list.ts
const { data } = await mail.emails.receiving.list();data?.data; // The latest 100 received messages, newest firstdata?.has_more; // true when there may be older ones
Differs from Resend

Safety

Everything in a received message was written by someone outside your control.

  • A visible From address is not proof of identity. Letterpier verifies that the message came from its own mail server, not what the sender claims.
  • Attachments aren’t scanned for malware; attachment scanning is not yet available. Never open or run them with privileges.
  • In the dashboard, previews run in a sandbox that blocks scripts, forms and remote content.
  • Don’t pass received content to tools or automations with broad permissions without validating it first.