Skip to content

Sending via the API

One endpoint sends transactional mail: POST /api/v1/emails. It needs an API key with the mail:send scope (create one in the dashboard — see AI agents & API keys) and a verified domain to send from.

The full request/response reference lives in the REST API reference — this page covers how the endpoint behaves.

A first send

Terminal window
curl https://faivelo.com/api/v1/emails \
-H "Authorization: Bearer $FAIVELO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "receipts@yourdomain.com",
"to": "customer@example.com",
"subject": "Your order is confirmed",
"html": "<p>Thanks for your order!</p>"
}'

The from address can be any local part on any of your verified domains — no mailbox has to exist for it. to, cc, and bcc each accept a single address or an array (up to 50 recipients per call). Provide html, text, or both.

The response includes an id. Keep it: it’s how you look the send up later.

Sending a stored template

Instead of inline content, pass a template alias and its variables:

{
"from": "receipts@yourdomain.com",
"to": "customer@example.com",
"templateAlias": "order-confirmation",
"variables": { "name": "Robin", "total": "$42.00" }
}

The template supplies the subject and body. Rendering is strict: if a variable the template references is missing, the call fails with 422 and nothing is sent — you get an error, not a receipt with a blank total.

If the template has an API-content slot, any html you pass renders inside the designed template rather than replacing it. See Templates for how slots and repeating sections work.

Safe retries with idempotency keys

Networks fail mid-request. To retry safely, send any unique string (up to 256 characters) as an Idempotency-Key header:

Terminal window
curl https://faivelo.com/api/v1/emails \
-H "Authorization: Bearer $FAIVELO_API_KEY" \
-H "Idempotency-Key: order-8412-confirmation" \
...

Two requests with the same key send at most one email — the second request gets the first one’s response back. Derive the key from the event that triggered the send (an order id, a reset-token id), not from a timestamp.

Attachments

Attach up to 10 files, 10 MB total, base64-encoded:

{
"attachments": [
{ "filename": "invoice.pdf", "content": "<base64>", "contentType": "application/pdf" }
]
}

Suppression

Recipients who previously hard-bounced or marked your mail as spam are on your suppression list and are dropped from the recipient set automatically. If every recipient is suppressed, the call fails with 422 and nothing is sent. This is protection, not an obstacle: sending to a known-bad address damages your domain’s deliverability for everyone you mail afterwards.

Tracking delivery

A send starts with status sent (accepted for delivery) and updates as events arrive:

StatusMeaning
sentAccepted for delivery; no events yet
deliveredThe receiving server accepted it
bouncedThe receiving server rejected it (the address is suppressed)
complainedThe recipient marked it as spam (the address is suppressed)
failedThe send could not be completed

Poll with GET /api/v1/emails/{id}, or — better for anything real-time — register a webhook and get pushed email.delivered, email.bounced, and email.complained events as they happen. The dashboard’s Transactional section shows the same log with full detail.

Limits

Transactional sends count against your plan’s monthly sending limit and your account’s earned daily allowance, shared with campaigns and every other send path. When a limit is reached the API returns an error naming the limit — see Sending limits for how allowances grow.

Sending from a mailbox instead

POST /api/v1/mailboxes/{address}/send sends as an existing mailbox, authenticated as that mailbox — the same path a mail app uses. It also accepts templateAlias. Use /emails for application mail from role addresses that don’t need a mailbox; use the mailbox send when the mail should come from a real person’s account.