Process inbound email
Lettermint sends each accepted inbound message as a signed message.inbound webhook.
A safe endpoint completes these actions:
- Verify the signature against the raw request body.
- Record the webhook delivery ID.
- Save the event or add it to a queue.
- Return a
2xxresponse.
Create the webhook
Open the inbound route
- In the dashboard, select Projects.
- Select your project and open Routes.
- Select the inbound route.
Add the endpoint
- Select Webhooks.
- Select Create webhook.
- Enter the public HTTPS URL for your endpoint.
Lettermint automatically subscribes the webhook to message.inbound.
Copy the signing secret after you create the webhook. Store the secret in your application secret manager.
Test a real message
Send an email to the route Inbound address. Open the webhook delivery log.
Check the request, response status, and retry attempts.
Verify and queue the event
Verify X-Lettermint-Signature against the unmodified request body before you parse the JSON. Follow Signed webhooks for complete framework examples.
The following handlers queue the verified event and return a response:
Prevent duplicate processing
Lettermint uses the same top-level id for each retry of one webhook delivery. Store this ID with a unique database constraint. You can also use the duplicate prevention feature in your queue.
Always verify the signature before you trust the event. Use the exact raw request body. A parsed and serialized body has different bytes.
Use the right address field
SMTP envelope data and visible message headers have different purposes. Use the envelope recipient to route a message. Use header addresses for display.
| Field | Use |
|---|---|
id | Identify one webhook delivery. Lettermint uses it again for retries. |
data.message_id | Identify the inbound message and its stored content. |
timestamp | Read the time that Lettermint created the webhook event. |
data.date | Read the time that Lettermint received the inbound message. |
data.recipient | Route the message by its SMTP envelope recipient. |
data.to and data.cc | Display the parsed To and Cc headers. |
data.envelope.mail_from | Check the SMTP envelope sender for delivery problems or bounces. |
data.from | Display the parsed From header. |
data.reply_to | Select the parsed reply address. This field can be null. |
Forwarding, aliases, and blind copies can change the envelope recipient. Therefore, it can differ from To or Cc.
Do not route a tenant or workflow from a visible header.
For a user reply, use reply_to if it has a value. Otherwise, use from.email.
Treat both fields as untrusted input. Do not send an automatic reply only because an address is in the payload.
Process content safely
- Sanitize
data.body.htmlbefore you display it. Email HTML can contain scripts, tracking resources, and false links. - Use
data.body.textif you do not need rich formatting. Each body field can benull. - Do not log message bodies, authentication headers, attachment content, or signed URLs.
- Check spam and authentication results before you start a sensitive action.
- Follow the attachment and raw email guide before you store files.
Return a response
Return a 2xx response after you save or queue the event. Do not wait for other processing to finish.
Lettermint retries a delivery after a timeout or a response outside the 2xx range. Your endpoint can receive the same event more than once. See Delivery and retries for the retry schedule.
For the complete event shape, field types, and nullable values, see the message.inbound webhook reference.
Troubleshooting
Why is there no webhook delivery?
- Confirm that the webhook belongs to the inbound route.
- Confirm that the endpoint is available through public HTTPS.
- For a generated address, confirm that the recipient is an exact match.
- For a custom domain, confirm that the domain is verified.
Why does signature verification fail?
Capture the raw request body before the JSON parser runs. Use the signing secret for this webhook.
Do not use an API token or a secret from a different webhook.
Why did my endpoint receive the event more than once?
Lettermint retries after a timeout or a response outside the 2xx range. Store the top-level id with a unique constraint.
Make sure that later operations can safely receive the same event again.
Why did the wrong workflow receive the message?
Route the message with data.recipient. For plus addressing, also use data.subaddress.
Do not use the first visible To address as the delivery destination.