Amba

Transactional Email

Send templated and ad-hoc transactional email from your project — register reusable templates, send to a user or a raw address, manage suppressions, and inspect delivery history.

Amba sends transactional email on your behalf — welcome mails, receipts, password resets, anything triggered by an app event. You register reusable HTML templates, then send by template name (to an end user or a raw address) or send a one-off ad-hoc message. Every send is recorded in a delivery log, and bounces / complaints are suppressed automatically so you don't keep mailing a dead address.

All routes mount under /v1/admin/projects/:projectId/email/* and require a developer Bearer token (your PAT). The amba_email_* MCP tools wrap the same surface for agentic use.

Endpoints

MethodPathDescription
POST/admin/projects/:projectId/email/templatesRegister or update a template (upsert).
GET/admin/projects/:projectId/email/templatesList templates.
GET/admin/projects/:projectId/email/templates/:nameDescribe one template.
PATCH/admin/projects/:projectId/email/templates/:nameUpdate a template's fields.
DELETE/admin/projects/:projectId/email/templates/:nameDelete a template.
POST/admin/projects/:projectId/email/sendSend (by template or ad-hoc).
GET/admin/projects/:projectId/email/deliveriesList deliveries (filter by template/status).
GET/admin/projects/:projectId/email/deliveries/:idDescribe one delivery.
POST/admin/projects/:projectId/email/suppressionsAdd a manual suppression.
GET/admin/projects/:projectId/email/suppressionsList suppressions.
DELETE/admin/projects/:projectId/email/suppressions/:emailRemove a suppression (unblock).

Templates

A template is a named subject + HTML body, with an optional plain-text body. Template names must match ^[a-z][a-z0-9_]*$. The body uses {{variable}} placeholders that are filled in from the data you pass at send time. When no text_body is set, a plain-text version is derived from the HTML automatically.

POST .../email/templates is an upsert — registering a template whose name already exists overwrites the subject and body.

curl -X POST 'https://api.amba.dev/v1/admin/projects/$PROJECT_ID/email/templates' \
  -H 'Authorization: Bearer $AMBA_PAT' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "welcome",
    "subject": "Welcome to {{app_name}}, {{first_name}}!",
    "html_body": "<h1>Hi {{first_name}}</h1><p>Thanks for joining {{app_name}}.</p>"
  }'
{ "data": { "name": "welcome", "created_at": "2026-05-27T10:00:00.000Z" } }
FieldTypeRequiredNotes
namestringyes^[a-z][a-z0-9_]*$.
subjectstringyesNon-empty. Supports {{variables}}.
html_bodystringyesSupports {{variables}}.
text_bodystringnoPlain-text fallback. Auto-derived if omitted.

Sending

POST .../email/send accepts one of three body shapes:

Templated, to an end user (looks up the user's email from your project):

curl -X POST 'https://api.amba.dev/v1/admin/projects/$PROJECT_ID/email/send' \
  -H 'Authorization: Bearer $AMBA_PAT' \
  -H 'Content-Type: application/json' \
  -d '{
    "to": { "user_id": "usr_123" },
    "template": "welcome",
    "data": { "first_name": "Sam", "app_name": "Acme" }
  }'

Templated, to a raw address:

{
  "to": { "email": "sam@example.com" },
  "template": "welcome",
  "data": { "first_name": "Sam", "app_name": "Acme" }
}

Ad-hoc (no template):

{
  "to": { "email": "sam@example.com" },
  "subject": "Your export is ready",
  "html": "<p>Download it <a href=\"https://...\">here</a>.</p>",
  "text": "Download it here: https://..."
}

The response carries the delivery id and status:

{
  "data": {
    "id": "del_abc",
    "status": "queued",
    "provider_message_id": "…"
  }
}

status is one of queued (accepted for delivery), suppressed (the recipient is on the suppression list — nothing was sent), or failed.

Deliveries

Every send writes a delivery row you can list and inspect. Filter by ?template=, ?status=, and ?limit= (max 200).

curl 'https://api.amba.dev/v1/admin/projects/$PROJECT_ID/email/deliveries?template=welcome&status=sent' \
  -H 'Authorization: Bearer $AMBA_PAT'
{
  "data": [
    {
      "id": "del_abc",
      "template": "welcome",
      "to_email": "sam@example.com",
      "app_user_id": "usr_123",
      "provider_message_id": "…",
      "status": "sent",
      "last_error": null,
      "created_at": "2026-05-27T10:00:01.000Z",
      "delivered_at": "2026-05-27T10:00:03.000Z"
    }
  ]
}

A delivery's status progresses through queued → sent, or lands on suppressed / failed. last_error carries a human-readable reason when a send fails.

Suppressions

Bounces and complaints add the address to the suppression list automatically; subsequent sends to a suppressed address return status: "suppressed" without contacting the recipient. You can also add a manual suppression, or remove one to unblock an address you've confirmed is valid again.

# Block an address manually
curl -X POST 'https://api.amba.dev/v1/admin/projects/$PROJECT_ID/email/suppressions' \
  -H 'Authorization: Bearer $AMBA_PAT' \
  -H 'Content-Type: application/json' \
  -d '{ "email": "bounced@example.com", "reason": "manual" }'
 
# Unblock it later
curl -X DELETE 'https://api.amba.dev/v1/admin/projects/$PROJECT_ID/email/suppressions/bounced@example.com' \
  -H 'Authorization: Bearer $AMBA_PAT'

reason is one of bounce, complaint, or manual (default manual). Filter the list with ?reason=.

From an agent or function

  • MCP — the amba_email_* tools (amba_email_send, the template and suppression operations, and delivery reads) wrap the endpoints above so an agent can send mail without touching HTTP. Pass your pat and the project_id.

  • Functions — call the send endpoint from a function using the injected env.AMBA_INTERNAL_TOKEN:

    await fetch(`${env.AMBA_API_URL}/v1/admin/projects/${env.AMBA_PROJECT_ID}/email/send`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${env.AMBA_INTERNAL_TOKEN}`,
        'content-type': 'application/json',
      },
      body: JSON.stringify({ to: { user_id: userId }, template: 'welcome', data: { first_name } }),
    });

Errors

CodeStatusMeaning
INVALID_TEMPLATE_NAME400Name doesn't match ^[a-z][a-z0-9_]*$.
TEMPLATE_NOT_FOUND404Send referenced a template that isn't registered.
USER_NOT_FOUND404to.user_id has no email on file.
EMAIL_VALIDATION400The send payload was malformed.
EMAIL_PROVIDER_REJECTED502 / 503The mail couldn't be handed off; safe to retry on 503.

On this page