Skip to content

Email

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.

FieldDescription
SMTP ConnectionPick 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.

FieldDescription
ToRecipient email addresses (one or more). Supports template expressions — e.g. {{ initial.email }} or {{ fetch-customers.body.contacts }}.
CCCarbon-copy recipients (optional)
BCCBlind carbon-copy recipients (optional)
Reply ToReply-to address (optional)
FromSender address (optional — overrides the connection’s default From)
SubjectSubject line. Supports templates.
HTML BodyEmail body as HTML. Supports templates.
Plain Text BodyPlain-text fallback for clients that don’t render HTML. At least one of HTML or Plain Text is required.

Add files to the email by listing them under Attachments. Each attachment has:

FieldDescription
File ContentThe file’s content. Usually templated from a file step, e.g. {{ generate-report.file.content }}.
File NameFilename including extension (e.g. invoice.pdf)
Encodingutf-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.pdf
Step 2 (email.send-email): attachments[0].content = {{ generate-pdf.file.content }}
attachments[0].fileName = invoice-{{ initial.invoiceId }}.pdf
attachments[0].encoding = base64
{{ 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.

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:

CodeCauseMeaning
SMTP_CONNECTION_FAILEDtransientCould not reach the SMTP server (DNS / connection refused / reset)
SMTP_TIMEOUTtransientSMTP session timed out
SMTP_SERVICE_UNAVAILABLEtransientServer returned 421 — service temporarily unavailable
SMTP_MAILBOX_BUSYtransientServer returned 450 / 451 — mailbox temporarily unavailable
SMTP_INSUFFICIENT_STORAGEtransientServer returned 452 — temporary storage exhaustion
SMTP_RATE_LIMITEDtransientServer is throttling — too many connections / messages
SMTP_AUTH_FAILEDpermanentAuthentication rejected (530 / 535) — credentials wrong, expired, or scoped out
SMTP_INVALID_RECIPIENTpermanentRecipient rejected (550 / 551 / 553) — mailbox missing or domain invalid
SMTP_MAILBOX_FULLpermanentRecipient mailbox is full (552)
SMTP_MESSAGE_REJECTEDpermanentMessage rejected (554) — typically spam filter or content policy
SMTP_INVALID_COMMANDpermanentSMTP protocol error (500–504) — usually a misconfigured transport
SMTP_POLICY_VIOLATIONpermanentSender policy rejection (521 / 556) — server doesn’t accept mail from this origin
SMTP_INVALID_SENDERpermanentSender address rejected — check the From header and SPF/DMARC config
EMAIL_UNKNOWN_ERRORunknownUnclassified — retried by default

Plus the warning-level code:

CodeMeaning
EMAIL_RECIPIENTS_REJECTEDSome 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.

SMTP Connection: postmark
To: {{ initial.userEmail }}
Subject: Your report is ready
HTML Body: <p>Hi {{ initial.userName }}, your report finished processing.</p>
Step 1 (file.generate-pdf): inline HTML → report.pdf
Step 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: base64
{% if send-email.sent %}
Logged delivery for {{ send-email.email.messageId }}
{% else %}
Failed: {{ send-email.errorCode }} — {{ send-email.error }}
{% endif %}