# Write by email

A case can run over email. A case is an email case when it has an email thread: sender address, contact address, subject and the context the agent needs.

## 1. Add and verify a sender domain

```bash title="cURL"
curl https://api.lelemon.cl/v2/email/domains \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  --json '{ "name": "mail.acme.com", "defaultFromLocal": "hello", "inboundAgentId": "agt_..." }'
```


Use a subdomain you own, such as `mail.acme.com`.

The response lists the DNS records to add: three `CNAME` records for DKIM and one `MX` record that routes replies to Lelemon. The domain is `pending` until your DNS answers and becomes `verified` on its own; `POST /v2/email/domains/{id}/verify` (or a `GET`) checks it right away, and it turns `failed` if the DKIM records are wrong. Register a subdomain such as `mail.acme.com`, not your main domain: you send from it and receive on it, and the `MX` record goes on that same name, so it does not take over the mail of `acme.com`. `inboundAgentId` is the agent that opens a case when a stranger writes to an address of the domain. Without it, mail from unknown senders is not routed to an agent.

Keys need `email:write`.

## 2. Send the first email

```bash title="cURL"
curl https://api.lelemon.cl/v2/email/messages \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Idempotency-Key: email-cust-88123-1" \
  --json '{
    "to": { "externalRef": "cust-88123" },
    "subject": "Your order",
    "text": "Hi Ana, your order is on its way.",
    "onReply": { "agentId": "agt_..." }
  }'
```


The response is `202`. A message goes through `queued`, `sent` and then `delivered` or `bounced`. A sender or recipient the email service rejects ends as `failed` with a `failure.code` such as `identity_not_verified`, `sandbox_recipient_not_verified`, `message_rejected`, `sending_paused`, `account_suspended` or `throttled`. `to` takes exactly one of `contactId`, `externalRef` or `email`. `from` must belong to a verified domain and defaults to the first one: `422 sender_domain_not_verified` otherwise. The agent in `onReply` needs the email channel.

You can also open an email case first with `POST /v2/cases` and `"channel": "email"`, then write with `POST /v2/cases/{id}/messages`. The first message of the case needs a `subject`: `422 subject_required` otherwise.

## Replies

Replies are matched to the thread by `In-Reply-To` and `References` against the Message-IDs of the emails Lelemon sent. The sender must be the address of the thread, and the email must pass DMARC. An email with a malformed or repeated `From` header is ignored. The agent answers like it does on any other channel, and you see the reply as a `message.created` event.

If a recipient marks an email as spam, the contact is opted out of the `email` channel, as if they had written `stop`.

## Before every send

Lelemon checks opt-outs for the `email` channel, by contact and by address, and the [recipient allowlist](/guides/best-practices#limit-who-can-receive-messages). A blocked send answers `422 contact_opted_out` or `422 recipient_not_allowed`.
