# Sending via the API

> Send transactional email with POST /api/v1/emails.

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](/developers/ai-agents)) and a [verified domain](/domains/verification) to send from.

The full request/response reference lives in the [REST API reference](https://faivelo.com/docs/api) — this page covers how the endpoint behaves.

## A first send

```bash
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](/transactional/templates) alias and its variables:

```json
{
  "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](/transactional/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:

```bash
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:

```json
{
  "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:

<table>
<thead>
  <tr>
    <th>
      Status
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        sent
      </code>
    </td>
    
    <td>
      Accepted for delivery; no events yet
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        delivered
      </code>
    </td>
    
    <td>
      The receiving server accepted it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bounced
      </code>
    </td>
    
    <td>
      The receiving server rejected it (the address is suppressed)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        complained
      </code>
    </td>
    
    <td>
      The recipient marked it as spam (the address is suppressed)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        failed
      </code>
    </td>
    
    <td>
      The send could not be completed
    </td>
  </tr>
</tbody>
</table>

Poll with `GET /api/v1/emails/{id}`, or — better for anything real-time — register a [webhook](/transactional/webhooks) 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](/sending/limits) (and, on a free trial, the sending domain's warm-up ramp). There is no daily allowance to earn: paid plans send at their quota from day one, and bounce or spam-report rates are what pause an account.

## 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.
