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

# Send Email

> Send an email with optional attachments via a selected organization email server

`action.email`, Send an email with optional attachments via a selected email server. At most 10 recipients combined across to, cc, and bcc.

Use Send Email when a workflow should deliver a result to people: a filled
spreadsheet, an extracted summary, or a short status note. Recipients, subject,
and body are authored on the step. The From address and transport come from the
**email server** you select — there is no workspace default and no fallback.

## Choose a server

`with.server` is required and must be a stable `ems_…` id. Create one or more
servers in **Settings → Email servers** (cloud and on-prem are the same), or
through the [email servers guide](/guides/email-servers) via API, CLI, or SDK.
Each organization can keep several Resend and SMTP servers; pick the one this
step should use. The step does not send through platform invite or inbound
Resend configuration.

Publishing fails if the id is missing, deleted, or disabled. At run time a
deleted server fails the step as not found, and a disabled server fails as
disabled — the run is not redirected to another server.

## Message limits

The body is plain text (at most 50KiB). Attachments must be files created
earlier in the **same run**, referenced by `fileId`. Files from other runs are
rejected. You can attach at most 10 files totaling 12MiB. `to`, `cc`, and `bcc`
accept at most 10 recipients combined. A recipient field can be a single
address, a comma-separated list of addresses, a template that resolves to one
or many addresses, or a nonempty array. Template expressions count as one
recipient at authoring time.

Pair it with [JSON to XLSX](/steps/transform/json-to-xlsx) when the email
should include a workbook:

```yaml theme={null}
- name: export-xlsx
  type: transform.json-to-xlsx
  with:
    filename: invoices.xlsx
    columns:
      - key: vendor
        header: Vendor
      - key: total
        header: Total
        type: number
    rows: "{{ steps.extract.output.lineItems }}"

- name: send-invoices
  type: action.email
  with:
    server: ems_01hexample0000000000000000
    to: "{{ input.opsEmail }}"
    cc:
      - finance@example.com
    replyTo: noreply@example.com
    subject: "Invoices for {{ steps.extract.output.vendor }}"
    text: |
      The invoice export is attached.
    attachments:
      - fileId: "{{ steps.export-xlsx.output.fileId }}"
        filename: invoices.xlsx
```

`filename` on an attachment is optional. Omit it to keep the stored file name.
You can also pass a bare file id string instead of an object.

## Delivery, evals, and cancellation

Sending email is a side effect, so this step is not durably retried. See
[Workflows → Retries](/concepts/workflows#retries).
Evaluation runs skip delivery, so evaluating a workflow cannot send mail to
real recipients. Async `action.invoke-workflow` children inherit that
suppression; the child run keeps the target workflow's trigger type. A skipped
step still returns the email output object, with `messageId` and `provider` set
to `null` and `serverId` set to the selected server, so later steps and eval
comparisons can read the shape without treating a message as sent.

Each send is budgeted at 30 seconds. Cancelling the run aborts an in-flight
send.

SMTP does not provide provider-side idempotency. If a worker stops after the
relay accepts a message but before the run records success, delivery status is
unknown and resuming the run may send a duplicate. Resend deduplicates the same
step attempt with a stable idempotency key.

## Configuration

Configuration goes inside the step's `with:` block.

<ParamField path="server" type="string" required>
  Outbound email server id (`ems_…`)
</ParamField>

<ParamField path="to" type="string | array<string>" required>
  Primary recipient(s). String, comma-separated list, or nonempty string array. Combined with cc and bcc, at most 10 recipients.
</ParamField>

<ParamField path="cc" type="string | array<string>">
  Carbon-copy recipient(s). String, comma-separated list, or string array. Combined with to and bcc, at most 10 recipients.
</ParamField>

<ParamField path="bcc" type="string | array<string>">
  Blind carbon-copy recipient(s). String, comma-separated list, or string array. Combined with to and cc, at most 10 recipients.
</ParamField>

<ParamField path="replyTo" type="string">
  Reply-To address (supports template expressions)
</ParamField>

<ParamField path="subject" type="string" required>
  Email subject
</ParamField>

<ParamField path="text" type="string" required>
  Plain-text email body (max 50KiB)
</ParamField>

<ParamField path="attachments" type="array<string | object>">
  Optional same-run attachments as \{ fileId, filename? } or a bare fileId string (12MiB combined)
</ParamField>

## Output

<ResponseField path="serverId" type="string" required>
  Email server that sent (or would have sent) the message
</ResponseField>

<ResponseField path="messageId" type="string | null" required>
  Provider message id. Null when delivery was skipped (evaluation runs).
</ResponseField>

<ResponseField path="provider" type="&#x22;resend&#x22; | &#x22;smtp&#x22; | null" required>
  Email provider that sent the message. Null when delivery was skipped.
</ResponseField>

<ResponseField path="to" type="array<string>" required>
  Normalized To recipients
</ResponseField>

<ResponseField path="cc" type="array<string>" required>
  Normalized Cc recipients
</ResponseField>

<ResponseField path="bcc" type="array<string>" required>
  Normalized Bcc recipients
</ResponseField>

<ResponseField path="attachmentCount" type="integer" required>
  Number of attachments sent
</ResponseField>

<ResponseField path="totalBytes" type="integer" required>
  Total attachment size in bytes
</ResponseField>
