Skip to main content
A transactional e-mail goes to one person because of what they have just done. It needs no list and no marketing consent, and it is logged and followed like a campaign. See Overview.

With a template

The cleanest way: the content lives in Lekalao, the application sends only the data. Create a template named order-confirmation (Transactional templates), then:
In the template:
Variables are escaped; loops run over lists of plain values. The whole syntax is in Personalisation.

Without a template

Send the HTML as it is:

The fields

Every address counts against the month’s allowance, copies included: an e-mail to one person in to and two in cc counts three. A transactional e-mail is written to someone, not broadcast: more than 50 addresses answer 422 too_many_recipients.

In several languages

A template can have one version per language (Languages). locale picks it: Pass your customer’s language as your application knows it: Lekalao falls back on its own.

The answer

The call is answered as soon as the e-mail is written down. It leaves from a queue a moment later. A refusal is an error like any other — message, code, doc_url — with the logged e-mail under data when there is one:
429 Too Many Requests
The e-mail itself:
202 Accepted
Treat 201 and 202 the same way: Lekalao has the e-mail. Keep its id; status tells whether it has left yet. Addresses on the suppression list are left out of to, cc and bcc without an error. The e-mail goes to the others, and fails only when no to is left.

Then, on the queue

Lekalao hands the e-mail to the provider. When the provider fails (a timeout, an error, a refusal), Lekalao tries again after 5 seconds, then 30 seconds, 2 minutes, 10 minutes and 30 minutes. It gives up after the sixth failed attempt, and in any case six hours after the call. status then becomes failed, with failure_code: provider_error and the provider’s last error in failure_reason. An e-mail whose job was lost on the way — a worker stopped, a queue emptied — is not left pending for ever: seven hours after the call, it becomes failed with failure_code: lost. Its content may not have been kept, so it is not sent again behind your back: send it again yourself. Transactional e-mails have a queue of their own: they never wait behind a campaign. The mailer’s rate limit still applies.

Follow it with webhooks

Rather than read the e-mail again and again, let Lekalao tell you. Subscribe an endpoint to the transactional.* events (Webhooks): Each one carries the e-mail as the API shows it: store its id with your order, and match on it. sent means the provider has it, not that it arrived. delivered_at is set when the provider reports a delivery, bounced_at when the address bounced for good, complained_at when a reader reported it as spam; status stays sent, and after a bounce failure_reason takes the provider’s reason. The address goes on the suppression list after a hard bounce or a complaint: the next e-mail to it will not leave. The same news also reaches mail.bounced and mail.complaint, with transactional_mail set to the e-mail’s id.

Or read it again

Without an endpoint, read the e-mail with GET /v1/transactional-mails/{id} until status is no longer pending. The token needs Read, which a Read and write token has.
Space the calls out. They count against the token’s limit (120 calls a minute by default), with everything else the token does:

When the call itself fails

A timeout or a 5xx leaves you not knowing whether Lekalao has the e-mail. Send the same call again with the same Idempotency-Key:
  • if Lekalao answered the first time, you get that first answer back, with Idempotent-Replay: true, and no second e-mail leaves. It is the answer as it was then, pending included: read the e-mail to know where it stands now. A 422 or a 429 is kept too: the same key gives the same refusal;
  • a 502 is not kept, so the same key sends again.
A key is kept 24 hours. See Idempotency.

Follow and send again

A resend takes the same recipients, variables, language, subject and sender address (from_email), and the same provider. It does not take the attachments, which are never kept, nor a from_name or reply_to given in the first call: the template’s, or the team’s, are used. A test e-mail sent again stays a test e-mail.

Without sending anything

A test key sends nothing: the e-mail is checked, rendered and logged, marked sent with "sandbox": true, and counted against nothing. Written to delivered@sink.lekalao.dev, bounced@sink.lekalao.dev and their siblings, it gets the provider’s answer and its webhooks, to try your handlers end to end.

Other roads