Handle inbound mail and replies
CamelMailer receives mail as well as sends it. To turn replies into something your app can act on, you point MX at the install, create a route that forwards matching mail to an HTTP endpoint, and verify the delivery on your side. The endpoint check is the same signature approach you already use for webhooks.
Three things stand between a reply landing and your app handling it: the receiving domain has to point at CamelMailer, a route has to say what happens to the message, and your endpoint has to accept and verify the POST. Work them in that order.
1. Point MX at the installation
The SMTP server listens as the MX for the domains you host and accepts mail at RCPT TO time. Publish an MX record for the receiving domain that points at your CamelMailer SMTP host. Without at least one route on the server, inbound mail is refused, so the MX and the route go together. The domain also needs to exist on the server; see Sending domains for adding and verifying it, and Inbound mail & routing for how a recipient is matched once mail arrives.
2. Create a route to an HTTP endpoint
A route is a small record on the mail server. Its name is the local part it answers to, its domain is where that address lives, and its modedecides the message's fate. For app processing you want Endpoint mode with an endpoint_url, so mail to that address is POSTed to your handler:
curl -s -X POST \
"$API/api/v2/admin/organizations/acme/servers/production/routes" \
-H "X-Admin-API-Key: $ADMIN_KEY" -H "Content-Type: application/json" \
-d '{
"name": "support",
"domain": "acme.example",
"mode": "Endpoint",
"endpoint_url": "https://app.acme.example/inbound"
}'The mode is the choice that matters. Endpoint forwards the message; the other modes leave it stored and readable in message history with nothing sent onward:
| Mode | What it does |
|---|---|
Endpoint | Accepts the mail, then POSTs it to your endpoint_url. |
Accept | Accepts and stores the mail, with nothing further to deliver. |
Hold | Accepts and keeps the mail on the server for inspection. |
Bounce | Accepts and stores the mail, with no separate bounce action today. |
Reject | Refuses the mail at RCPT TO with 550, so the sender learns right away. |
The endpoint URL is validated: it must be an HTTP(S) URL or exactly internal://dmarc-reports for DMARC report ingestion. Updating a route changes its name and mode only, so to move the endpoint you delete and recreate the route. The full route model, the direct-to-route token address, and the note that a route matches one exact local part rather than a wildcard are in Inbound mail & routing.
3. Receive and verify at your endpoint
For a route in Endpoint mode, the worker POSTs a JSON envelope carrying the raw message base64-encoded. Your endpoint owns everything after that: parse the raw message, open a ticket, reply, or file it. A 2xx response completes the delivery, and any other status retries with backoff until the attempt limit:
{
"message": {
"id": 1234,
"token": "…",
"rcpt_to": "support@acme.example",
"mail_from": "customer@example.com",
"bounce": false
},
"raw_base64": "…the full RFC 822 message, base64-encoded…"
}Inbound HTTP delivery shares the signing and retry model that CamelMailer's other HTTP callbacks use, so the receiver you build here is the receiver you already know. Answer fast with a 2xx, verify the RSA signature against the raw request body before you parse it, and key your processing on a stable id so a retry becomes a no-op. Rather than repeat the receiver, reuse the working one: Receive webhooks walks the fast-ack handler and the signature check line by line, and Webhooks documents the signature algorithm and the retry schedule.
4. Let bounces and replies flow back
Replies reach a route by their address. Bounces take a different door: they arrive on the installation's shared return-path domain, where the SMTP intake recognizes the recipient by its return-path token, matches it to the originating server, and processes the DSN. That return-path domain is installation-wide and shared across every sending domain, so the per-domain records hold no return-path entry. To route a domain's bounces under its own subdomain, publish a CNAME from the custom return-path prefix to the return-path domain as an operator step. The return-path model and that CNAME are in Sending domains.
- Replies hit a route you named and POST to your endpoint, exactly as above.
- Bounces land on the return path and are classified into hard, soft, or undetermined, so the observability API can break failures down by category.
- Spam complaints that an ISP delivers as a feedback report are recognized by their envelope and turned into a stream-scoped complaint for the recipient who complained. See Suppressions.
You read inbound messages through the same endpoints as outbound mail: GET /api/v2/server/messages/{id} returns the message with its delivery attempts, which is where a completed, held, or failed endpoint delivery shows up.
