Skip to main content
This guide builds a support inbox on Lettr: customers email an address such as support@yourcompany.com, each message reaches your application as a ticket, and agents’ answers go back into the same conversation. Inbound email posts each message to your application as JSON and the send API sends the reply, so there is no mail server to run and no IMAP mailbox to poll.

How the Flow Works

Lettr only moves mail: it accepts a message for your inbound domain, posts it to your forwarding URL and stops there. Everything else happens in your handler:
Lettr does no inbound spam filtering. There is no spam score in the payload and no setting in the dashboard. Every message accepted for your inbound domain reaches your endpoint, spam included.

Step 1: Pick an Address Layout

Use a dedicated subdomain for the support inbox, such as support.example.com. Mail to any address at an inbound domain goes to the same forwarding URL, so help@, billing@ and anything@ all arrive at one endpoint and are routed in code. A subdomain also keeps your root domain’s MX records pointing at your regular mailbox provider (Google Workspace, Microsoft 365), so staff email is unaffected.
If customers should see support@example.com rather than support@support.example.com, forward from your existing mailbox to an address on the inbound domain, or point the root domain’s MX records at Lettr only if that domain doesn’t need a regular mailbox.

Step 2: Build the Receiving Endpoint First

The forwarding URL is required. When you create the inbound domain, Lettr tests it with a POST request and refuses to create the domain unless your endpoint answers with a 2xx status. The endpoint therefore has to exist and be publicly reachable before the domain is created in the dashboard. Start with an endpoint that verifies the auth token and acknowledges the request:
An endpoint behind a firewall or WAF that challenges unknown clients (a Cloudflare 403, for example) fails the test: the dashboard shows the status your endpoint returned and the domain is not created. Allow server-to-server POST requests on that path. Lettr’s mail infrastructure publishes no fixed source IP addresses, so rely on the auth token rather than an IP allowlist. See Security.

Step 3: Create the Inbound Domain and MX Records

1

Create the inbound domain

In the dashboard, go to Domains → Inbound and click Create Inbound Domain. Enter the Domain Name (support.example.com), your Forwarding URL (https://yourapp.com/webhooks/inbound) and an Auth token. The token is sent to your endpoint in the X-MessageSystems-Webhook-Token header.
2

Add the MX records

Add the three MX records the dashboard shows to your DNS. For a subdomain, set the host to support.All three are required.
3

Verify

Click Verify MX Records on the domain’s page. When both the MX records and the forwarding URL are in place, the page shows This domain is receiving mail.
A domain with MX records but no forwarding URL shows No forwarding URL, and mail sent to it is accepted and then discarded. Check the status before you announce the address. To look at incoming mail before DNS is set up, the inbound sandbox gives every team an address for inspecting what arrives, but sandbox mail is never forwarded to your endpoint. To test the handler end to end, create a real inbound domain on a test subdomain. Full details are in the Inbound Email Setup guide.

Step 4: Turn a Relay Message into a Ticket

Each request body is a JSON array. Every element carries one message in msys.relay_message: There is no attachments array. Attachments live only inside email_rfc822, so parse it with a MIME library such as mailparser when you need them. See Attachments.
Resolve the customer’s address in that order: the Reply-To header, then the address in friendly_from, with msg_from only as a last fallback. Store each inbound Message-ID, because Step 6 needs it to reply in-thread. Lettr’s send API returns a request_id, not the Message-ID of the email it sent, so the stored IDs come from the customer’s messages. For the complete field list see Email Parsing.

Step 5: Filter Spam in Your Handler

A public support address receives spam, bounce notices and auto-replies, and all of it reaches the handler. Three rules keep the queue clean:
  • Always answer 2xx, including for messages you drop. The mail was already accepted by the time it reaches you, so an error status cannot reject it back to the sender. It only makes the delivery to your endpoint look failed.
  • Treat sender fields as claims. msg_from and friendly_from are set by whoever sent the message. When the receiving server added an Authentication-Results header, a dmarc=fail is a strong signal. A missing header means “unknown”, not “pass”.
  • Skip automatic mail. Don’t open tickets for auto-replies, and never auto-reply to them, or two autoresponders can loop.
More patterns, including quarantining instead of dropping, are in Spam Filtering.

Step 6: Reply with the Send API

Replies written in your ticket UI go out through POST /emails. Three details make the reply land in the customer’s existing conversation:
  • from must be an address on a verified sending domain. The inbound domain is for receiving. Sending is configured separately under sending domains.
  • reply_to is the support address on your inbound domain, so the customer’s next message comes back to your endpoint. To tie the reply to a ticket, put the ticket ID in it with a plus-style address such as support+t-4821@support.example.com, then read it from rcpt_to. Use lowercase letters, digits and hyphens in the ID.
  • headers can carry In-Reply-To and References. The send API allows both: it blocks only a fixed list of headers it manages itself (such as From, To, Subject, Message-ID, Reply-To and List-Unsubscribe) and accepts up to 10 custom headers, each a string up to 998 characters. Set In-Reply-To to the customer’s last Message-ID and References to their References value followed by that Message-ID.
The Idempotency-Key header means a retry after a network error doesn’t email the customer twice. Keep the Re: prefix and the original subject too: some clients thread by subject when headers are missing or rewritten.
Threading with In-Reply-To and References depends on the customer’s mail client. Most honor it, but Lettr can’t guarantee how a given client groups messages. The reply address and subject are the reliable parts. Don’t depend on custom headers such as X-Ticket-ID for matching: clients don’t copy them into replies.
If the same from address will receive customer replies too, don’t send replies from no-reply@. Use a monitored address, and set reply_to so answers reach the inbox you built.

Common Problems

A firewall or WAF is blocking the test POST. Allow server-to-server requests on the forwarding URL’s path and try again. The domain is created only after your endpoint answers 2xx.
Check the domain’s status. No forwarding URL means mail is accepted and discarded. Open the Mail Forwarding card and add one. Also confirm your handler returns 2xx and doesn’t throw on messages with only an HTML part.
A domain without MX records falls back to its A record, so mail lands on your web host. Add all three MX records and re-run Verify MX Records. Allow time for DNS propagation.
Match on more than one signal: the ticket ID in the reply_to address first, then In-Reply-To and References against stored Message-ID values, then a subject token. Customers edit subjects and some clients drop headers.

Inbound Email Setup

Create the inbound domain, MX records and forwarding URL

Reply Tracking

Match replies to the emails you sent

Spam Filtering

Build spam handling into your forwarding URL

Inbound Security

Verify the auth token and validate content