> ## Documentation Index
> Fetch the complete documentation index at: https://docs.drin.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Send an email

> POST /v1/emails — queue a single transactional email.

Queue a single transactional email. The request returns immediately with a message id; delivery happens asynchronously and is reported over webhooks.

**`POST /v1/emails`**

Provide a `from` address on a verified domain (or the shared onboarding domain in test mode), at least one recipient, a subject, and a body — `html`, `text`, or both. Or send a stored template with `templateId` + `data` and omit the subject and body.

## Body parameters

<ParamField body="from" type="object" required>
  The sender, `{ email, name? }`. The `email` domain must be verified for this project (or the shared onboarding domain in test mode).
</ParamField>

<ParamField body="to" type="object[]" required>
  One or more recipients, each `{ email, name? }`. At least one is required.
</ParamField>

<ParamField body="subject" type="string">
  The subject line. Required unless `templateId` is given.
</ParamField>

<ParamField body="html" type="string">
  The HTML body. Provide `html`, `text`, or both.
</ParamField>

<ParamField body="text" type="string">
  The plain-text body. Recommended alongside `html` for deliverability.
</ParamField>

<ParamField body="cc" type="object[]">
  Carbon-copy recipients.
</ParamField>

<ParamField body="bcc" type="object[]">
  Blind carbon-copy recipients.
</ParamField>

<ParamField body="replyTo" type="object[]">
  Addresses to set in the `Reply-To` header.
</ParamField>

<ParamField body="templateId" type="string">
  Send a stored template by id or slug. With a template, `subject`/`html`/`text` are optional and merge variables come from `data`.
</ParamField>

<ParamField body="data" type="object">
  Merge variables for the template (`{{handlebars}}` placeholders).
</ParamField>

<ParamField body="headers" type="object">
  Extra headers to set on the message, as a string-to-string map.
</ParamField>

<ParamField body="tags" type="object[]">
  Labels for filtering and analytics, each `{ name, value }`.
</ParamField>

<ParamField body="attachments" type="object[]">
  Files to attach, each `{ filename, content, contentType? }` where `content` is base64-encoded bytes.
</ParamField>

<ParamField body="scheduledAt" type="string">
  Schedule the send for a future time (ISO 8601). Omit to send immediately.
</ParamField>

## Headers

<ParamField header="Idempotency-Key" type="header">
  Make this POST safe to retry — the same key replays the original result for 24 hours, per project. See [Idempotency & retries](/idempotency).
</ParamField>

<ParamField header="X-Drin-Product" type="header">
  Names the sending project for account-wide keys (alias: `X-Drin-Sender`). Project-scoped keys may omit it.
</ParamField>

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.drin.run/v1/emails \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "from": { "email": "onboarding@yourdomain.com", "name": "Acme" },
      "to": [{ "email": "you@example.com" }],
      "subject": "Hello from Drin",
      "html": "<p>It works!</p>",
      "text": "It works!"
    }'
  ```

  ```typescript Node.js theme={null}
  import { DrinClient } from "@drin00/sdk";

  const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });

  const { id, status } = await drin.emails.send({
    from: { email: "onboarding@yourdomain.com", name: "Acme" },
    to: [{ email: "you@example.com" }],
    subject: "Hello from Drin",
    html: "<p>It works!</p>",
    text: "It works!",
  });
  ```
</CodeGroup>

## Response

`202 Accepted` — the message was queued. Track its lifecycle with [`GET /v1/emails/{id}`](/api-reference/emails/get) or over [webhooks](/webhooks).

```json 202 Accepted theme={null}
{
  "id": "msg_01HZX9K3T2QF7P0M4N8B6C5D",
  "status": "queued"
}
```

<Warning>
  **Errors.** `422` `validation_error` if a field is malformed (`param` names the field); `409` `suppressed` if every recipient is on the suppression list; `409` `conflict` on an idempotency replay with a different body. See [Errors](/api-reference/errors).
</Warning>

<Note>
  **Test mode.** The shared onboarding domain can only deliver to your own address. To email anyone from your own brand, [verify a domain](/domains) first.
</Note>
