The Email step (email.send-email) sends an email message over any SMTP server. Use it for transactional notifications, post-run reports, file delivery, or anything else where the destination is a mailbox.
It works with any SMTP provider — Gmail, Office 365, Postmark, SendGrid, Amazon SES, Mailgun, your own Postfix box, etc. Either pick an SMTP connection you’ve already configured, or paste credentials directly into the step.
Configuration
Section titled “Configuration”SMTP Connection
Section titled “SMTP Connection”| Field | Description |
|---|---|
| SMTP Connection | Pick a saved connection of type smtp. The connection holds the host, port, credentials, and default From address — the step injects them at runtime. |
If you don’t have a connection set up, the step also accepts inline credentials via the credentials field (host, port, username, password, secure flag, optional default From). Connections are preferred — they keep the password out of the workflow JSON and let you swap providers without editing every step.
Email Configuration
Section titled “Email Configuration”| Field | Description |
|---|---|
| To | Recipient email addresses (one or more). Supports template expressions — e.g. {{ initial.email }} or {{ fetch-customers.body.contacts }}. |
| CC | Carbon-copy recipients (optional) |
| BCC | Blind carbon-copy recipients (optional) |
| Reply To | Reply-to address (optional) |
| From | Sender address (optional — overrides the connection’s default From) |
| Subject | Subject line. Supports templates. |
| HTML Body | Email body as HTML. Supports templates. |
| Plain Text Body | Plain-text fallback for clients that don’t render HTML. At least one of HTML or Plain Text is required. |
Attachments
Section titled “Attachments”Add files to the email by listing them under Attachments. Each attachment has:
| Field | Description |
|---|---|
| File Content | The file’s content. Usually templated from a file step, e.g. {{ generate-report.file.content }}. |
| File Name | Filename including extension (e.g. invoice.pdf) |
| Encoding | utf-8 for text files (CSV, JSON, HTML), base64 for binary files (PDF, images, ZIPs) |
A common pattern is to chain a file step into the email step:
Step 1 (file.generate-pdf): inline HTML → invoice.pdfStep 2 (email.send-email): attachments[0].content = {{ generate-pdf.file.content }} attachments[0].fileName = invoice-{{ initial.invoiceId }}.pdf attachments[0].encoding = base64Output
Section titled “Output”{{ send-email.sent }} // true if delivery succeeded{{ send-email.email.messageId }} // SMTP message ID returned by the server{{ send-email.email.accepted }} // array of accepted recipients{{ send-email.email.rejected }} // array of rejected recipients (partial failure){{ send-email.email.response }} // raw SMTP response string{{ send-email.error }} // error message (only when sent === false){{ send-email.errorCode }} // taxonomy error code (see below)If some recipients are accepted and others are rejected, sent is still true but the step is flagged with an EMAIL_RECIPIENTS_REJECTED warning visible in $meta.operationalErrors and the $errors array.
Error handling
Section titled “Error handling”The Email step retries transient SMTP failures (connection failures, timeouts, 4xx SMTP responses, rate limits) with exponential backoff and fails fast on permanent ones (auth failure, invalid recipient, mailbox full, message rejected). Tune this in the step’s Error Handling panel.
On failure, errorCode is one of:
| Code | Cause | Meaning |
|---|---|---|
SMTP_CONNECTION_FAILED | transient | Could not reach the SMTP server (DNS / connection refused / reset) |
SMTP_TIMEOUT | transient | SMTP session timed out |
SMTP_SERVICE_UNAVAILABLE | transient | Server returned 421 — service temporarily unavailable |
SMTP_MAILBOX_BUSY | transient | Server returned 450 / 451 — mailbox temporarily unavailable |
SMTP_INSUFFICIENT_STORAGE | transient | Server returned 452 — temporary storage exhaustion |
SMTP_RATE_LIMITED | transient | Server is throttling — too many connections / messages |
SMTP_AUTH_FAILED | permanent | Authentication rejected (530 / 535) — credentials wrong, expired, or scoped out |
SMTP_INVALID_RECIPIENT | permanent | Recipient rejected (550 / 551 / 553) — mailbox missing or domain invalid |
SMTP_MAILBOX_FULL | permanent | Recipient mailbox is full (552) |
SMTP_MESSAGE_REJECTED | permanent | Message rejected (554) — typically spam filter or content policy |
SMTP_INVALID_COMMAND | permanent | SMTP protocol error (500–504) — usually a misconfigured transport |
SMTP_POLICY_VIOLATION | permanent | Sender policy rejection (521 / 556) — server doesn’t accept mail from this origin |
SMTP_INVALID_SENDER | permanent | Sender address rejected — check the From header and SPF/DMARC config |
EMAIL_UNKNOWN_ERROR | unknown | Unclassified — retried by default |
Plus the warning-level code:
| Code | Meaning |
|---|---|
EMAIL_RECIPIENTS_REJECTED | Some recipients were accepted, others rejected. The step succeeds but logs a warning. |
See Error Handling → Which step types have taxonomies for how the taxonomy plugs into the retry policy and the Custom retry mode editor.
Common patterns
Section titled “Common patterns”Send a simple notification
Section titled “Send a simple notification”SMTP Connection: postmarkTo: {{ initial.userEmail }}Subject: Your report is readyHTML Body: <p>Hi {{ initial.userName }}, your report finished processing.</p>Send a generated PDF as an attachment
Section titled “Send a generated PDF as an attachment”Step 1 (file.generate-pdf): inline HTML → report.pdfStep 2 (email.send-email): To: {{ initial.recipientEmail }} Subject: Monthly report — {{ initial.month }} HTML Body: <p>Your report is attached.</p> Attachments[0].content: {{ generate-pdf.file.content }} Attachments[0].fileName: report-{{ initial.month }}.pdf Attachments[0].encoding: base64Branch on delivery success
Section titled “Branch on delivery success”{% if send-email.sent %} Logged delivery for {{ send-email.email.messageId }}{% else %} Failed: {{ send-email.errorCode }} — {{ send-email.error }}{% endif %}