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

# Emails

> Create durable EmailSend records from saved Email Templates or rendered JSX Email content.

Use `emails.send` when your backend needs to send one email to one primary recipient, such as an order confirmation, password reset, account invite, receipt, shipping update, lead magnet delivery, or product update.

`emails.send` creates one durable `EmailSend`. The REST route is `POST /api/v1/emails`; each accepted request freezes one content source, resolves one primary Profile, freezes the effective envelope, enqueues delivery, and returns the `EmailSend` status handle.

Use `emails.batchSend` when your backend already has several independent EmailSends to create at once. Each item has its own primary recipient, `data`, idempotency key, durable status record, and result.

If you are deciding between one email, batch email, event-triggered Journeys, Broadcasts, or Newsletter Issues, start with [Which API should I use?](/docs/which-api-should-i-use).

<Note>
  EmailSend accepts exactly one content source: a saved Email Template
  (`templateKey` or `templateId`) or a rendered JSX Email wrapper (`content`).
  Do not send raw top-level `html`, `text`, `react`, inline template content,
  Template aliases, Template slugs, Template names, or locale-key resolution in
  v1.
</Note>

## Prerequisites

* A saved, active Email Template, or rendered content produced by the official TypeScript SDK JSX Email helper or another HTML renderer.
* A verified sending domain.
* A SenderProfile on that verified sending domain.
* A purpose-scoped API Key with `emails:write` to send and `emails:read` to retrieve status.
* A caller-generated `Idempotency-Key` for every `emails.send` request, or an item-level `idempotencyKey` for every `emails.batchSend` item.

EmailSend accepts saved Email Templates and rendered JSX Email content. Purpose determines send policy:

* `Transactional` templates are treated as transactional sends and do not require `subscriptionGroupId`.
* `Promotional`, `Newsletter`, `LeadMagnetDelivery`, and `General` templates are treated as marketing-class sends and require `subscriptionGroupId`.
* Rendered content must include an explicit `purpose`: `"transactional"`, `"marketing"`, or `"newsletter"`. Marketing and newsletter rendered sends require `subscriptionGroupId`; Segmentflow injects the compliance footer automatically.

```ts theme={null}
import Segmentflow from "@segmentflow/segmentflow-typescript";

const client = new Segmentflow({
  apiKey: process.env.SEGMENTFLOW_API_KEY,
});
```

## Send one email

Use `templateKey` to choose a saved Email Template. Use envelope fields such as `from`, `to.email`, `subject`, `replyTo`, `cc`, and `bcc` the same way you would in an email provider API, while still keeping the content template-backed.

```ts theme={null}
const send = await client.v1.emails.send({
  "idempotency-key": "order-confirmation:ord_9421",
  templateKey: "receipt-default",
  from: "Acme Receipts <receipts@example.com>",
  to: {
    email: "alex@example.com",
  },
  subject: "Receipt for order ord_9421",
  replyTo: "Acme Support <support@example.com>",
  cc: "Billing <billing@example.com>",
  bcc: ["audit@example.com"],
  data: {
    orderId: "ord_9421",
    firstName: "Alex",
    orderTotal: "49.00",
    receiptUrl: "https://app.example.com/orders/ord_9421/receipt",
  },
});
```

The response is the durable `EmailSend` status handle. Store `send.id` in your system and use it for reconciliation.

```json theme={null}
{
  "id": "018f7a35-1111-7111-8111-111111111111",
  "status": "Queued",
  "templateId": "2c2f9e13-7d0a-4b10-9f4b-35f6d96d7a70",
  "source": {
    "type": "template",
    "templateId": "2c2f9e13-7d0a-4b10-9f4b-35f6d96d7a70"
  },
  "frozenEmailInstanceId": "018f7a35-3333-7333-8333-333333333333",
  "senderProfileId": "018f7a35-4444-7444-8444-444444444444",
  "from": {
    "email": "receipts@example.com",
    "name": "Acme Receipts"
  },
  "replyTo": {
    "email": "support@example.com",
    "name": "Acme Support"
  },
  "subject": "Receipt for order ord_9421",
  "subscriptionGroupId": null,
  "profileId": "018f7a35-5555-7555-8555-555555555555",
  "to": {
    "email": "alex@example.com"
  },
  "cc": [
    {
      "email": "billing@example.com",
      "name": "Billing"
    }
  ],
  "messageId": null,
  "skippedReason": null,
  "failureReason": null,
  "failureMessage": null,
  "createdAt": "2026-05-29T00:00:00.000Z",
  "updatedAt": "2026-05-29T00:00:00.000Z"
}
```

Ordinary create and retrieve responses echo the safe effective envelope: `from`, `replyTo`, `subject`, `to`, `cc`, and `subscriptionGroupId`. They do not echo `bcc`.

## Send rendered JSX Email

For Node.js applications, use the official TypeScript SDK JSX Email helper. The SDK renders JSX locally with `jsx-email` and sends Segmentflow a rendered `content` payload. Other API clients can render HTML themselves and pass `content: { type: "rendered", renderer: "jsx-email", html, text? }`.

Segmentflow freezes the rendered HTML/text as the send history; it does not create a fake reusable Template.

Install the SDK, `jsx-email`, React, and the JSX Email peer plugins in the app that renders email components. The JSX Email helper requires Node.js 22 or later because `jsx-email` 3 requires Node.js 22 or later.

```bash theme={null}
pnpm add @segmentflow/segmentflow-typescript jsx-email react react-dom @jsx-email/plugin-inline @jsx-email/plugin-minify @jsx-email/plugin-pretty
```

```tsx theme={null}
import Segmentflow from "@segmentflow/segmentflow-typescript";
import { Body, Button, Html, Link, Text } from "jsx-email";

function ProductUpdateEmail() {
  return (
    <Html>
      <Body>
        <Text>New workflow templates are ready.</Text>
        <Button href="https://app.example.com/templates/new">
          View templates
        </Button>
        <Link href="https://app.example.com/account/security" data-no-track="">
          Security settings
        </Link>
      </Body>
    </Html>
  );
}

const client = new Segmentflow({
  apiKey: process.env.SEGMENTFLOW_API_KEY,
});

const send = await client.v1.emails.sendJsxEmail({
  "idempotency-key": "product-update:2026-07-02:alex@example.com",
  purpose: "marketing",
  from: "Acme Updates <updates@example.com>",
  to: { email: "alex@example.com" },
  subject: "New workflow templates are ready",
  subscriptionGroupId: "018f7a35-6666-7666-8666-666666666666",
  jsxEmail: <ProductUpdateEmail />,
  text: "New workflow templates are ready.",
  contentMetadata: {
    componentName: "ProductUpdateEmail",
  },
});
```

For `purpose: "marketing"` and `purpose: "newsletter"`, Segmentflow automatically injects unsubscribe, preferences, and web-view footer links at send time. For `purpose: "transactional"`, Segmentflow does not inject a marketing footer. The helper records source metadata with `@segmentflow/segmentflow-typescript/jsx-email`.

Other SDKs and direct HTTP integrations can send the rendered wrapper directly when they already rendered HTML in their app:

```json theme={null}
{
  "purpose": "marketing",
  "subscriptionGroupId": "019bf964-302b-7a7f-add2-589343ef9f39",
  "subject": "New arrivals",
  "to": {
    "email": "buyer@example.com"
  },
  "content": {
    "type": "rendered",
    "renderer": "jsx-email",
    "html": "<html><body><p>New arrivals are live.</p></body></html>",
    "text": "New arrivals are live."
  }
}
```

Rendered content rules:

* `content.type` must be `"rendered"` and `content.renderer` must be `"jsx-email"`.
* `purpose` is required for rendered sends.
* `subject` is required because there is no saved Template subject fallback.
* `content.text` is optional. Segmentflow generates a plain-text fallback from HTML when omitted.
* Rendered HTML is limited to 1 MiB UTF-8, rendered text to 256 KiB, and the combined rendered body to 1.25 MiB.
* Customer headers are not accepted through the TypeScript SDK JSX Email helper or EmailSend API.
* Marketing and newsletter rendered sends use Segmentflow-injected unsubscribe, preferences, and web-view footer links.
* Web-view HTML shows the compliance-injected content before click tracking rewrites. Provider HTML may include Segmentflow tracking redirects.

Rendered responses return `templateId: null` and a rendered source:

```json theme={null}
{
  "id": "018f7a35-1111-7111-8111-111111111111",
  "status": "Queued",
  "templateId": null,
  "source": {
    "type": "rendered",
    "renderer": "jsx-email"
  },
  "frozenEmailInstanceId": "018f7a35-3333-7333-8333-333333333333"
}
```

## Recipient resolution

Every EmailSend has exactly one primary recipient. That primary recipient resolves to one Profile and creates at most one Message.

Use `to.email` when you know the recipient email address:

```ts theme={null}
await client.v1.emails.send({
  "idempotency-key": "order-confirmation:ord_9421",
  templateKey: "receipt-default",
  to: { email: "alex@example.com" },
  data: { orderId: "ord_9421" },
});
```

Add `to.externalId` when you want Segmentflow to link or reconcile the Profile against your stable customer id:

```ts theme={null}
await client.v1.emails.send({
  "idempotency-key": "order-confirmation:ord_9421",
  templateKey: "receipt-default",
  to: {
    email: "alex@example.com",
    externalId: "shopify:customer_123",
  },
  data: { orderId: "ord_9421" },
});
```

Use top-level `profileId` when you already know the Segmentflow Profile. If you omit `to.email`, Segmentflow uses the Profile's canonical email for delivery.

```ts theme={null}
await client.v1.emails.send({
  "idempotency-key": "order-confirmation:ord_9421",
  templateKey: "receipt-default",
  profileId: "018f7a35-5555-7555-8555-555555555555",
  data: { orderId: "ord_9421" },
});
```

`profileId` and `to.email` must refer to the same Profile. `to.externalId` alone must resolve to an existing Profile; include `to.email` when the send may need to create a new Profile.

EmailSend does not update durable Profile properties. Use the Profiles API or `events.track` `profile.properties` when you need future segmentation or personalization state.

## Content source, sender, and subject

Pass exactly one of `templateKey`, `templateId`, or `content`.

For saved Templates, pass the stable `templateKey`. `templateId` remains available as an advanced fallback when you need to pin an exact database id. v1 does not resolve aliases, slugs, locale keys, or template names. If you edit the Template after a send is accepted, the existing EmailSend still uses the Frozen Email Instance created for that send.

For rendered JSX Email in Node.js, call `client.v1.emails.sendJsxEmail`. The TypeScript SDK renders locally with `jsx-email` and sends Segmentflow a rendered `content` payload. Other clients can pass `content: { type: "rendered", renderer: "jsx-email", html, text? }` directly. Segmentflow stores source metadata on the frozen Email Instance so dashboards and API responses can show that the send came from rendered JSX Email without pretending it used a saved Template.

SenderProfile resolution is deterministic:

1. Use request `from` or `senderProfileId` when present.
2. If both `from` and `senderProfileId` are present, they must resolve to the same SenderProfile.
3. For saved Template sends, otherwise use the SenderProfile saved on the Template.
4. For rendered sends, `from` or `senderProfileId` is required.
5. If no compatible SenderProfile on a verified domain resolves, the send is rejected or skipped with `InvalidSenderProfile`.

`from` accepts a bare email or `"Name <email@example.com>"`. The email must exactly match a verified SenderProfile email. A verified domain does not authorize arbitrary local-parts.

`replyTo` accepts one bare email or `"Name <email@example.com>"`. It may differ from `from`, but it must use a verified sender domain owned by your Organization.

Request-level `subject` is final text. Segmentflow freezes it onto the EmailSend and does not interpolate it. If you omit `subject` on a saved Template send, the saved Template subject is frozen instead. Rendered sends must include `subject`.

## Copied recipients

Use `cc` and `bcc` to copy operational recipients on the same provider envelope.

```ts theme={null}
await client.v1.emails.send({
  "idempotency-key": "order-confirmation:ord_9421",
  templateKey: "receipt-default",
  to: { email: "alex@example.com" },
  cc: ["Billing <billing@example.com>", "ops@example.com"],
  bcc: "audit@example.com",
  data: { orderId: "ord_9421" },
});
```

`cc` and `bcc` accept a single friendly-address string or an array of friendly-address strings. Segmentflow validates and stores them for provider handoff.

Copied recipients are envelope-only in v1:

* They do not create Profiles.
* They do not create Messages.
* They do not own personalization.
* They do not receive separate Delivery Events.
* Primary Profile erasure, suppression, and subscription checks apply to the primary recipient, not copied recipients.

`cc` is returned in ordinary create and retrieve responses for operational visibility. `bcc` is stored for delivery and retry behavior, but ordinary API responses, Resource Events, and dashboard list rows do not expose it.

## Batch independent sends

`emails.batchSend` accepts up to 100 independent items. Use shared `defaults` for fields that are identical across the batch, then override specific fields on each item.

```ts theme={null}
const batch = await client.v1.emails.batchSend({
  defaults: {
    templateKey: "receipt-default",
    from: "Acme Receipts <receipts@example.com>",
    subject: "Your receipt is ready",
    replyTo: "Acme Support <support@example.com>",
    tracking: { clicks: false },
  },
  items: [
    {
      idempotencyKey: "order-confirmation:ord_9421",
      to: {
        email: "alex@example.com",
        externalId: "shopify:customer_123",
      },
      data: {
        orderId: "ord_9421",
        orderTotal: "49.00",
      },
      cc: "Billing <billing@example.com>",
    },
    {
      idempotencyKey: "order-confirmation:ord_9422",
      to: { email: "sam@example.com" },
      subject: "Receipt for order ord_9422",
      data: {
        orderId: "ord_9422",
        orderTotal: "129.00",
      },
      bcc: "audit@example.com",
    },
  ],
});
```

Batch defaults can include `templateKey`, `templateId`, `content`, `purpose`, `from`, `senderProfileId`, `subject`, `replyTo`, `subscriptionGroupId`, and `tracking`. Each item can override those defaults.

After defaults merge with an item, each item must have exactly one effective content source: `templateKey`, `templateId`, or `content`. If an item supplies any source field, that item source replaces the default source fields.

Batch defaults cannot include `profileId`, `to`, `data`, `cc`, or `bcc`. Recipient identity, one-send data, and copied recipients must stay item-specific.

Malformed batch envelopes fail the whole request. Item-specific validation, idempotency, sender, template, or recipient failures return `Rejected` results for those items without rolling back accepted items.

`emails.batchSend` is not a campaign sender. Use Broadcasts or Newsletter Issues when the system should compute or manage an audience, scheduling, campaign review, compliance controls, or one shared message sent to a segment.

## Tracking

```ts theme={null}
await client.v1.emails.send({
  "idempotency-key": "password-reset:user_123:2026-05-29T12:00Z",
  templateKey: "password-reset",
  to: { email: "sam@example.com" },
  data: {
    resetUrl: "https://app.example.com/reset?token=secret-token",
    expiresInMinutes: 30,
  },
  tracking: {
    clicks: false,
  },
});
```

Click tracking is enabled by default. Set `tracking.clicks: false` for sends where links should not pass through a redirect. In rendered HTML, add `data-no-track`, `data-segmentflow-no-track`, or `ses:no-track` to individual links that must never be rewritten. Compliance links are never tracked. Open tracking is disabled for email in v1 and cannot be enabled per request.

## Subscription groups

Pass `subscriptionGroupId` when this email should respect a specific SubscriptionGroup. If the primary recipient is not subscribed according to that group's OptIn or OptOut policy, Segmentflow creates a durable skipped `EmailSend` with `skippedReason: "ProfileUnsubscribed"`.

Omit `subscriptionGroupId` only for transactional sends, such as password resets or receipts. For `Promotional`, `Newsletter`, `LeadMagnetDelivery`, and `General` saved Templates, `subscriptionGroupId` is required so Segmentflow can apply consent policy. For rendered content, `purpose: "marketing"` and `purpose: "newsletter"` require `subscriptionGroupId`; Segmentflow injects compliance links automatically.

## Retrieve status

Store the returned `id` in your system. Use it as the durable handle for reconciliation when Resource Events are delayed or unavailable.

```ts theme={null}
const current = await client.v1.emails.retrieve(send.id);
```

Retrieve responses follow the same envelope echo rules as create responses: safe fields such as `from`, `replyTo`, `subject`, `to`, and `cc` are returned, while `bcc` is not.

## Idempotency

Every `emails.send` request requires an `Idempotency-Key` header. Every `emails.batchSend` item requires an `idempotencyKey` field. Choose a stable key from your business event, such as `order-confirmation:ord_9421` or `password-reset:user_123:<reset-request-id>`.

Idempotency is scoped to your Organization and the full effective request. The effective request includes the resolved recipient identity, content source, sender, subject, `replyTo`, copied recipients, tracking settings, subscription gate, and one-send `data`. For rendered content, the fingerprint includes rendered HTML, rendered text, purpose, purpose-resolved compliance behavior, tracking settings, and source metadata.

* Reusing the same key with the same effective request returns the existing EmailSend with its current status.
* Reusing the same key with a different effective request returns `409 Conflict` for `emails.send` or a `Rejected` item result for `emails.batchSend`.
* Keys remain reserved for the lifetime of the EmailSend record.
* Idempotent replay does not re-enqueue, re-render, recreate a Message, or send a duplicate email.

Moving a batch field between `defaults` and an item does not change idempotency when the merged effective request is the same.

## Status lifecycle

| Status       | Meaning                                                                                                                             |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `Queued`     | The request was accepted, persisted, and enqueued for worker processing.                                                            |
| `Processing` | A worker is resolving, rendering, or sending the email.                                                                             |
| `Sent`       | Segmentflow's delivery adapter accepted the email. This is not an inbox-delivery guarantee.                                         |
| `Failed`     | The send was accepted for processing, but rendering, provider submission, or worker execution failed.                               |
| `Skipped`    | Segmentflow intentionally did not attempt provider delivery because of policy, suppression, subscription, admission, or rate limit. |

Delivery, open, click, bounce, and complaint outcomes are separate Delivery Records or Message events. They do not mutate the EmailSend lifecycle status.

## Raw HTTP

```bash theme={null}
curl https://api.segmentflow.ai/api/v1/emails \
  -H "x-api-key: $SEGMENTFLOW_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: order-confirmation:ord_9421" \
  -d '{
    "templateKey": "receipt-default",
    "from": "Acme Receipts <receipts@example.com>",
    "to": {
      "email": "alex@example.com",
      "externalId": "shopify:customer_123"
    },
    "subject": "Receipt for order ord_9421",
    "replyTo": "Acme Support <support@example.com>",
    "cc": "Billing <billing@example.com>",
    "bcc": ["audit@example.com"],
    "data": {
      "orderId": "ord_9421",
      "orderTotal": "49.00"
    }
  }'
```

See the [API Reference](/docs/api-reference/overview) for the complete request and response schema.
