Set up a sending domain
Register a domain your product owns, publish the DNS records Lessly Mail returns, and get it to verified so your confirmations and receipts can leave from it.
Lessly Mail sends only from domains you control. You register one, publish the DNS records Mail returns, and Mail re-checks those records until the domain is verified — from then on you can send from any address at it.
Everything on this page belongs to one product. A domain registered in one product is invisible to every other product.
Choose what to register
A product may register a domain only underneath a domain it already owns: the apex itself, or any subdomain of it. A name that is neither is rejected with 422 and the error name domain_not_governed.
| Register | Use when | |
|---|---|---|
A dedicated subdomain, e.g. mail.example.com | Your product sends system email | (Recommended) — keeps your product’s mail apart from the mail your team sends by hand |
The apex, e.g. example.com | You need messages to come from the bare domain | Puts your product’s mail DNS in the same zone as everything else you publish |
Register the domain
-
List the apexes you may use. An agent calls
mail_domain_allowed_list; over REST it isGET /mail/domains/allowedon the Mail API.[ { "domainId": "d3f1…", "domain": "example.com", "mode": "external", "extensionCount": 2 } ] -
Register it. An agent calls
mail_domain_create; over REST,POST /mail/domains.nameis required.regiondecides where the domain’s sending identity lives, defaults toeu-north-1, and is fixed at creation — to move a domain to another region, delete it and register it again.{ "name": "mail.example.com", "region": "eu-north-1" }The response is the domain,
statusset topending, with the records to publish:{ "id": "8e2c…", "name": "mail.example.com", "region": "eu-north-1", "status": "pending", "records": [ { "type": "CNAME", "name": "abc123._domainkey.mail.example.com", "value": "<the signing host Mail returns>", "purpose": "dkim", "required": true }, { "type": "TXT", "name": "_dmarc.mail.example.com", "value": "v=DMARC1; p=none;", "purpose": "dmarc", "required": false } ], "verificationStartedAt": "2026-08-02T09:00:00.000Z", "verifiedAt": null, "active": false } -
Publish every record in
recordsat your DNS provider, exactly as given, values included. Keep the domain’sid— you need it to ask for a check. -
Wait, or check now. An agent calls
mail_domain_verify; over REST,POST /mail/domains/:id/verify. The reply adds aresolvedflag to each record, so you can see which ones Mail can read yet.resolvedappears only in that reply —GET /mail/domains/:idreturns the records without it.
Two things stop a registration. The name is already registered in that region, by any product —
409. Or the host is already claimed elsewhere in your product —409with the error codehost_taken, naming what holds it. Neither leaves anything behind: a failed create is rolled back and the same name can be retried.
The DNS records
Every record carries a type, the name to create it at, its value, a purpose and whether it is required. MX records also carry a priority.
| Purpose | Record | Required | What it does |
|---|---|---|---|
dkim | One CNAME per signing key, at <token>._domainkey.<your-domain> | Yes | Lets Mail sign your messages, and lets receiving servers confirm the signature really comes from your domain. Without them a domain never verifies. |
dmarc | A TXT at _dmarc.<your-domain>, value v=DMARC1; p=none; | No | Tells receiving servers what to do with mail claiming to be from your domain but not signed by it. p=none only asks them to report, so it is safe to publish on day one. Mail does not check it, but publishing it is what lets you tighten the policy later. |
spf, return-path-mx | A TXT and an MX on a send. subdomain | Yes, where present | Carry the address receiving servers return bounces to. A domain registered today does not list them; if yours does, they must stay published. |
Statuses
| Status | Meaning |
|---|---|
not_started | Registered, not yet checked. |
pending | Being checked. The required records are not all readable yet. |
verified | All required records are in place. The domain can send. |
temporary_failure | A check failed in a way that may clear by itself. Mail keeps trying. |
failed | Verification will not complete on its own. |
A domain also carries active, which is true only when the domain is verified and its claim on the host is live. active is the honest answer to “can I send from this domain right now”.
Sending from a domain that is not verified fails with 422 and the error name invalid_from_address. Sending from a domain that is not registered in your product fails with 404.
When verification does not complete
Verification starts by itself the moment the domain is created, and Mail re-checks every unsettled domain once a minute. How long it takes is decided by your DNS provider, not by Mail: most domains verify within a few minutes of the records going live, and a provider with a long TTL can take several hours. Mail keeps trying for 72 hours from verificationStartedAt; past that the domain becomes failed.
| Cause | What to do |
|---|---|
| The records are not published yet | Ask for an immediate check and read resolved on each record. Every record with required: true must be true. |
| A record was altered | Some providers append the zone name to a record you enter, turning abc123._domainkey.mail.example.com into abc123._domainkey.mail.example.com.example.com. Compare what you published against name, character for character. |
| The value was trimmed | DKIM values end in a trailing dot. Publish the value exactly as Mail returns it. |
| DNS has not propagated | Nothing to do but wait. Mail re-checks every minute on its own. |
temporary_failure | A lookup failed in a way that may clear. Mail keeps checking, and the domain moves on by itself once the records answer. |
A failed domain does not resume. Fix the records, then delete the domain and register it again — a fresh registration restarts the 72-hour window.
Change how the domain sends
An agent calls mail_domain_update; over REST, PATCH /mail/domains/:id. Send only the fields you want to change. name and region are fixed at creation and cannot be patched.
| Field | Values | Default | What it does |
|---|---|---|---|
openTracking | true / false | false | Adds a tracking pixel to HTML bodies so opens are reported. |
clickTracking | true / false | false | Rewrites links so clicks are reported. |
trackingSubdomain | a host name or null | null | The host tracked links and the pixel are served from. |
tls | opportunistic / enforced | opportunistic | enforced refuses to deliver to a server that will not encrypt the connection. |
Both tracking settings are off by default, which is usually where they stay for confirmations, receipts and access-recovery mail.
To read domains back: mail_domain_list or GET /mail/domains for every domain in the product, and mail_domain_get or GET /mail/domains/:id for one with its records. An id that belongs to another product is a 404.
Remove a domain
An agent calls mail_domain_delete; over REST, DELETE /mail/domains/:id. The response is { "deleted": true }. The domain stops being able to send at once and its claim on the host is released, so the name is free to register again — in this product or another. Emails already sent from the domain keep their history.
Next steps
- Create and rotate a sending key: authorise your code to send, and lock the key to the domain you just verified.
- Send a message: the fields a send accepts, and what comes back.
- Handle bounces and protect your sending: what a rejection does to your suppression list and your reputation.
- How system email works: where domain setup sits in the seven-step flow.