Send product email from Elixir. Supervise retries, not duplicate sends.
Send transactional email and publish lifecycle events from BEAM applications and jobs with the Send & Retain API.
✓Call through Req or Finch from a context, then retry safely in Oban.
One request, complete trace
Request
POST /api/v1/emails
Authorization: Bearer aem_…
Idempotency-Key: welcome-8f21
{ "to": "[email protected]", "template": "welcome" }
202 Accepted
{ "id": "msg_7ca31", "status": "queued" }
[ 01 / 04 ]Client contract
Put the HTTP contract behind an OTP-friendly boundary.
Use Req or Finch
Call the stable JSON API from the HTTP stack already supervised by your application.
Send typed payloads
Plain maps and structs for sender, recipients, template, and props.
Handle delivery events
Cache the raw body in a custom Plug body reader — Plug.Parsers consumes it otherwise.
[ 02 / 04 ]Server quickstart
Queue one email from Elixir.
Keep the key server-side; the idempotency key makes retries safe.
Call from a context or supervised job and retain the accepted message ID.
Req.post!("https://sendandretain.com/api/v1/emails",
headers: [
authorization: "Bearer #{System.fetch_env!("AEM_KEY")}",
idempotency_key: "welcome-#{user.id}"
],
json: %{to: user.email, template: "welcome"}
)Accepted response
{ "id": "msg_7ca31", "status": "queued" }[ 03 / 04 ]Quickstart
Let the BEAM supervise work without repeating the email.
Wrap the request in a context
Centralize the endpoint, key, timeout, and structured error handling.
Run retried work in Oban
Use a stable idempotency key so a restarted or retried job maps to one logical message.
Keep the accepted ID
Store the identifier in job metadata or domain state for later delivery checks.
Production checklist
- Secrets stay server-side
- Timeout and retry policy
- Idempotent webhook handler
- Structured error logging
[ 04 / 04 ]Related
Use Elixir in context
Questions people ask us
Queue your first Elixir email.
Call from a context or Oban job and keep the accepted ID through delivery.