Node type: email-smtp | Resource: email | Operation: send — This node sends one email per execution. To send personalised emails to a list of recipients, iterate over the list using a Loop node and execute one Email / SMTP node per iteration.
When to Use
-
Transactional email delivery: After a customer completes a checkout, submits an application, or triggers any event that requires immediate confirmation, use this node to send a personalised, branded email. The subject, body, and recipient address are all driven by workflow data — no template system outside the workflow is needed.
-
Password reset and account verification: When a user requests a password reset or registers a new account, generate a time-limited token in an upstream step and include it in the email body via expression. The node dispatches the email to the address pulled directly from the workflow context.
-
Operational alerting and notifications: When a workflow encounters an error, breaches an SLA threshold, or completes a batch job, alert the responsible team by email. Dynamic recipient lists allow routing to on-call engineers, managers, or distribution groups based on the type and severity of the event.
-
Report and document delivery: At the conclusion of a nightly processing workflow, compose an HTML email containing a formatted summary table and send it to stakeholders. Attach generated PDFs, spreadsheets, or other binary artefacts produced earlier in the workflow run.
-
Approval and review notification: When a purchase request, leave application, or document enters an approval stage in a Human-in-the-Loop workflow, notify the designated approver with a deep link to the review portal. Include a structured summary of the request populated from workflow variables so the approver has full context without leaving the email.
-
Onboarding and lifecycle emails: Send a welcome email immediately after new user creation, or trigger a re-engagement email when a user reaches an inactivity threshold. Personalise the message with the recipient's name, account details, and direct action links from workflow data.
Configuration
Connection
| Field | Required | Description |
Host |
Required |
SMTP server hostname or IP address. Common values: smtp.gmail.com (Gmail), smtp.office365.com (Microsoft 365), smtp.sendgrid.net (SendGrid), smtp.mailgun.org (Mailgun). Do not include port or protocol in this field. |
Port |
Required |
SMTP server port. Standard ports: 587 (STARTTLS — recommended for modern servers), 465 (SMTPS / implicit SSL), 25 (unencrypted — avoid in production). Range: 1–65535. |
Username |
Required |
SMTP authentication username. Usually the sender's email address (e.g. noreply@yourcompany.com). For relay services (SendGrid, Mailgun), this is typically a fixed API username such as apikey or smtp@mg.yourdomain.com. |
Password |
Required |
SMTP authentication password, sourced via the BizFirst Credentials Manager. Reference as {{ $credentials.smtpPassword }}. Never paste a raw password string into this field. For Gmail, use an App Password generated from Google Account settings, not the account login password. |
Ssl |
Optional |
When true, connects using implicit SSL/TLS (typically on port 465). Use this for servers that do not support STARTTLS. Mutually exclusive with UseTls. |
UseTls |
Default: true |
When true, upgrades the connection to TLS using the STARTTLS command after the initial plaintext handshake. The standard for port 587. Recommended for all production configurations. |
AllowUnauthorized |
Default: false |
When true, skips TLS certificate validation. For development or internal servers with self-signed certificates only. Never enable in production — this disables protection against man-in-the-middle attacks. |
Envelope
| Field | Required | Description |
FromEmail |
Required |
Sender email address. Must be a valid RFC 5321 address. Should match or be authorised by the SMTP username's domain to pass SPF checks. For shared sending infrastructure, use the authenticated account address (e.g. noreply@yourcompany.com). |
FromName |
Optional |
Display name shown in the recipient's email client alongside the From address. E.g. "Acme Notifications", "BizFirst Platform". Supports expressions for dynamic sender names. |
ToEmail |
Required |
Primary recipient email address or addresses. For multiple recipients, supply a comma-separated string or a BizFirst expression that resolves to a comma-separated list. E.g. {{ $json.customerEmail }} or {{ $json.recipients | join(",") }}. |
CcEmail |
Optional |
CC (carbon copy) recipient(s). Same format as ToEmail. CC recipients are visible to all recipients. Leave empty if not required. |
BccEmail |
Optional |
BCC (blind carbon copy) recipient(s). Not visible to other recipients. Useful for compliance archiving, silent audit copies, or internal logging mailboxes. |
ReplyToEmail |
Optional |
Address that replies are directed to, when different from FromEmail. E.g. for automated notifications sent from noreply@, set ReplyToEmail to support@yourcompany.com so replies reach the helpdesk. |
Subject |
Required |
Email subject line. Fully expression-driven. E.g. "Order Confirmation #{{ $json.orderId }}", "Action Required: {{ $json.requestTitle }}". Keep under 60 characters for optimal display in mobile email clients. |
Body
| Field | Required | Description |
Html |
At least one |
HTML body of the email. Supports full HTML markup, inline CSS, and BizFirst expressions. Use for rich, branded transactional emails. Sanitise any user-supplied content before interpolation to prevent XSS vulnerabilities in email clients. |
Text |
At least one |
Plain text fallback body. Displayed by email clients that do not render HTML, and used by spam filters that prefer multi-part messages. Best practice: always supply both Text and Html. The text version should convey the same core information without relying on formatting. |
At least one body field required: The node requires either Html or Text (or both) to be non-empty. Providing both is strongly recommended — a plain text fallback improves deliverability and accessibility.
Options
| Field | Required | Description |
Priority |
Optional |
Email priority hint. Accepted values: "high", "normal", "low". Maps to the X-Priority and Importance MIME headers. Use "high" only for genuinely urgent emails — overuse degrades deliverability reputation. |
Headers |
Optional |
Dictionary of additional custom MIME headers to include in the outgoing message. E.g. {"X-Workflow-ID": "{{ $context.workflowId }}", "X-Environment": "production"}. Useful for tracking, compliance, and integration with email archiving systems. |
Attachments |
Optional |
Array of attachment objects. Each attachment must include: FileName (string, e.g. "invoice.pdf"), ContentType (MIME type, e.g. "application/pdf"), Content (base64-encoded file content). Optional: Cid (Content-ID for inline images referenced in the HTML body as <img src="cid:logoCid">). |
Sample Configuration
{
"Host": "smtp.gmail.com",
"Port": 587,
"Username": "noreply@yourcompany.com",
"Password": "{{ $credentials.smtpPassword }}",
"UseTls": true,
"FromEmail": "noreply@yourcompany.com",
"FromName": "YourCompany",
"ToEmail": "{{ $json.customerEmail }}",
"Subject": "Order Confirmation #{{ $json.orderId }}",
"Html": "<h2>Thank you for your order!</h2><p>Order #{{ $json.orderId }} has been received and is being processed.</p>",
"Text": "Thank you for your order! Order #{{ $json.orderId }} has been received and is being processed."
}
Validation Errors
| Error Code | Cause & Resolution |
MISSING_HOST | The Host field is empty or evaluates to an empty string. Provide a valid SMTP hostname. |
INVALID_PORT | The Port value is outside the valid range of 1–65535. Must be a positive integer. |
MISSING_FROM_EMAIL | The FromEmail field is empty. A sender address is required for all outgoing messages. |
INVALID_FROM_EMAIL | The FromEmail value is not a valid email address format. Check for missing @, invalid domain, or malformed expression. |
MISSING_TO_EMAIL | The ToEmail field is empty. At least one recipient is required. |
INVALID_TO_EMAIL | One or more addresses in ToEmail are not valid email format. Validate the recipient address before this node executes. |
INVALID_CC_EMAIL | One or more addresses in CcEmail are not valid email format. |
INVALID_BCC_EMAIL | One or more addresses in BccEmail are not valid email format. |
INVALID_PRIORITY | The Priority value is not one of the accepted values: "high", "normal", "low". |
INVALID_ATTACHMENT_* | An entry in the Attachments array is missing a required field (FileName, ContentType, or Content), or the Content value is not valid base64. |
Output
Success Port — Email Accepted by SMTP Server
Routes here when the SMTP server accepts the message for delivery. Note that SMTP acceptance does not guarantee delivery to the recipient's inbox — the remote MTA may still bounce or filter the message.
| Field | Type | Description |
status | string | Always "success" on this port. |
errorCode | string | Always null on the success port. |
resource | string | Always "email". |
operation | string | Always "send". |
messageId | string | SMTP Message-ID assigned by the sending server, e.g. "<CABcd1234@smtp.gmail.com>". Use this for delivery tracking, deduplication, or referencing the message in subsequent workflow steps. |
accepted | array | Array of email addresses that the SMTP server confirmed as accepted. In a successful send, this will contain all specified recipients. E.g. ["customer@example.com"]. |
rejected | array | Array of addresses rejected by the SMTP server at the envelope level. A non-empty rejected list on the success port indicates partial delivery — some recipients were accepted, others were not. |
response | string | The raw SMTP server response string, e.g. "250 2.0.0 OK". Useful for debugging SMTP relay behaviour or logging the server's exact confirmation message. |
Partial delivery: When sending to multiple recipients, the SMTP server may accept some addresses and reject others. The success port fires if at least one recipient was accepted. Always inspect rejected in downstream logic — a non-empty array means some recipients did not receive the email.
Error Port — SMTP Failures & Connection Errors
Routes here when the SMTP server rejects the entire message, authentication fails, or a network error prevents delivery. Error codes indicate the specific failure class for targeted handling.
| Error Code | Description |
AUTH_FAILED | SMTP authentication rejected. Check Username and Password credentials, and ensure the account has SMTP access enabled (e.g. Gmail requires an App Password if 2FA is active). |
SMTP_COMMAND_REJECTED | The SMTP server rejected a protocol command (e.g. RCPT TO or MAIL FROM). The message field contains the SMTP error code and reason. |
SMTP_PROTOCOL_ERROR | An unexpected protocol-level error occurred during the SMTP transaction. May indicate a server version incompatibility or an intermediate proxy issue. |
TLS_HANDSHAKE_FAILED | TLS negotiation failed. Check that Port and TLS settings (Ssl / UseTls) match the server's requirements. May also indicate an expired or untrusted server certificate. |
CONNECTION_FAILED | The node could not establish a TCP connection to the SMTP server on the specified Host and Port. Check hostname, port, firewall rules, and network connectivity from the workflow executor. |
ALL_RECIPIENTS_FAILED | Every recipient address was rejected at the SMTP level. No message was delivered. Verify recipient address validity and check whether the sender domain is on a blocklist. |
CANCELLED | The workflow execution was cancelled or the node was interrupted before completion. |
UNEXPECTED_ERROR | An unclassified internal error occurred. Inspect the message field for details and contact support if the issue persists. |
Sample Output
{
"status": "success",
"messageId": "<CABcd1234@smtp.gmail.com>",
"accepted": ["customer@example.com"],
"rejected": [],
"response": "250 2.0.0 OK",
"resource": "email",
"operation": "send"
}
Expression Reference
Assuming this node is named sendEmail in the workflow. Replace sendEmail with your actual node name.
| Expression | Returns | When to Use |
{{ $output.sendEmail.messageId }} | string | Store the SMTP message ID for delivery tracking, audit logging, or correlating with bounce notifications in a downstream step. |
{{ $output.sendEmail.accepted }} | array | Log or inspect which recipients successfully received the message. Use length(accepted) to count successful deliveries. |
{{ $output.sendEmail.rejected }} | array | Check for partial delivery failures. Branch with an If node: if length(rejected) > 0, route to a retry or alert path. |
{{ $output.sendEmail.response }} | string | Log the raw SMTP server response for audit trails or debugging SMTP relay behaviour in staging environments. |
{{ $output.sendEmail.status }} | string | Confirm node completion status ("success"). Useful when chaining multiple email nodes and tracking completion state in a shared workflow context variable. |
Node Policies & GuardRails
| Policy Area | Recommendation |
| Credential Management | The SMTP password must be stored in the BizFirst Credentials Manager. Reference it via {{ $credentials.credentialName }}. Never paste a raw password string into the Password field, workflow configuration, or version control. Rotate credentials immediately if a raw password is accidentally committed or logged. |
| SPF & DKIM Alignment | FromEmail must be authorised by the sending domain's SPF record to avoid spam classification. Ideally it matches the SMTP Username domain. For custom From addresses on a different domain, configure DKIM signing and DMARC alignment on that domain. Misaligned sender domains significantly harm deliverability. |
| HTML Body Sanitisation | If any part of the Html body is constructed from user-supplied input, sanitise it before interpolation. Unsanitised HTML can introduce malicious content into the email body (XSS in email clients, phishing markup). Use an encoding function or a sanitisation Code node upstream. |
| Recipient Address Validation | Validate ToEmail format before calling this node. An invalid address causes the node to route to the error port with a validation code rather than an SMTP error, which is harder to diagnose in production. Use a Condition node with a regex expression upstream if addresses come from user input. |
| Rate Limit Awareness | SMTP providers enforce daily and per-minute sending limits. Gmail (free): ~500/day. Google Workspace: ~2,000/day. Office 365: 10,000/day (subject to per-minute bursting limits). When sending to large lists, add a Delay node between email sends and stay well below daily limits to avoid account suspension. |
| Bulk vs. Transactional | This node is intended for transactional and operational emails (1-to-1 sends triggered by events). For marketing campaigns, bulk newsletters, or high-volume promotional emails, use a dedicated Email Service Provider (ESP) such as SendGrid, Mailgun, or Mailjet via their respective API nodes — not SMTP direct sending. Bulk sends via SMTP risk IP reputation damage. |
| Priority Header Usage | Set Priority to "high" only for genuinely urgent messages such as security alerts or immediate action-required notifications. Overusing high priority on routine emails trains recipients and spam filters to ignore the signal, and may worsen deliverability reputation on some mail platforms. |
| Development / Test Routing | In development and staging environments, override ToEmail with a fixed test mailbox address using an environment-conditional expression: {{ $env.NODE_ENV == 'production' ? $json.recipientEmail : 'test-inbox@yourcompany.com' }}. This prevents accidental sends to real users from non-production workflows. |
| TLS Configuration | For port 587, use UseTls: true with Ssl: false. For port 465, use Ssl: true with UseTls: false. Mixing these causes connection failures. Never enable AllowUnauthorized in production — it disables the certificate validation that protects against credential interception. |
SMTP password security: A raw SMTP password in workflow configuration is a critical secret exposure. If a password is ever entered directly into the Password field instead of via {{ $credentials.* }}, treat it as compromised — revoke and rotate immediately, audit workflow logs to confirm it was not captured, and review access control on the workflow configuration.
AllowUnauthorized in production: Setting AllowUnauthorized: true disables TLS certificate validation. This allows a man-in-the-middle attacker to intercept SMTP credentials and email content in transit. Only use in isolated development environments with no sensitive data. Never deploy with this flag enabled.
Workflow Examples
Example 1 — Order Confirmation Email (HTML + Text)
Send a branded HTML confirmation email to a customer immediately after order placement. The subject, recipient, order details, and body are all populated from workflow variables.
{
"nodeType": "email-smtp",
"name": "sendOrderConfirmation",
"Host": "smtp.gmail.com",
"Port": 587,
"Username": "noreply@yourcompany.com",
"Password": "{{ $credentials.smtpPassword }}",
"UseTls": true,
"FromEmail": "noreply@yourcompany.com",
"FromName": "Acme Orders",
"ToEmail": "{{ $json.customer.email }}",
"Subject": "Order Confirmation #{{ $json.orderId }}",
"Html": "<!DOCTYPE html><html><body style='font-family:sans-serif;'><h2>Thank you, {{ $json.customer.firstName }}!</h2><p>Your order <strong>#{{ $json.orderId }}</strong> has been received and is now being processed.</p><p>Estimated delivery: <strong>{{ $json.estimatedDelivery }}</strong></p></body></html>",
"Text": "Thank you, {{ $json.customer.firstName }}! Your order #{{ $json.orderId }} has been received. Estimated delivery: {{ $json.estimatedDelivery }}."
}
// On success port:
// {{ $output.sendOrderConfirmation.messageId }} → store for audit trail
// {{ $output.sendOrderConfirmation.accepted }} → verify recipient was accepted
Example 2 — Operations Alert with Priority
Alert the on-call engineering team when a workflow detects a service anomaly. Sends with high priority, includes both the alert summary and a link to the monitoring dashboard.
{
"nodeType": "email-smtp",
"name": "alertOpsTeam",
"Host": "smtp.office365.com",
"Port": 587,
"Username": "alerts@yourcompany.com",
"Password": "{{ $credentials.alertsSmtpPassword }}",
"UseTls": true,
"FromEmail": "alerts@yourcompany.com",
"FromName": "BizFirst Monitoring",
"ToEmail": "oncall@yourcompany.com",
"CcEmail": "engineering-lead@yourcompany.com",
"Subject": "[ALERT] {{ $json.alertLevel }}: {{ $json.serviceName }} — {{ $json.metricName }} breached threshold",
"Priority": "high",
"Html": "<h3 style='color:#c82045;'>Service Alert</h3><table border='1' cellpadding='8'><tr><td><strong>Service</strong></td><td>{{ $json.serviceName }}</td></tr><tr><td><strong>Metric</strong></td><td>{{ $json.metricName }}</td></tr><tr><td><strong>Value</strong></td><td>{{ $json.metricValue }}</td></tr><tr><td><strong>Threshold</strong></td><td>{{ $json.threshold }}</td></tr></table><p><a href='{{ $json.dashboardUrl }}'>Open Monitoring Dashboard</a></p>",
"Text": "ALERT: {{ $json.serviceName }} — {{ $json.metricName }} is {{ $json.metricValue }}, threshold is {{ $json.threshold }}. Dashboard: {{ $json.dashboardUrl }}"
}
Example 3 — Approval Notification with Deep Link
Notify an approver that a purchase request is awaiting their review. Includes the request summary and a direct link to the approval portal. Uses ReplyToEmail so replies go to the requestor.
{
"nodeType": "email-smtp",
"name": "notifyApprover",
"Host": "smtp.sendgrid.net",
"Port": 587,
"Username": "apikey",
"Password": "{{ $credentials.sendgridApiKey }}",
"UseTls": true,
"FromEmail": "workflow@yourcompany.com",
"FromName": "BizFirst Approvals",
"ToEmail": "{{ $json.approver.email }}",
"ReplyToEmail": "{{ $json.requestor.email }}",
"Subject": "Approval Required: {{ $json.requestTitle }} — {{ $json.requestAmount | currency }}",
"Html": "<h2>Purchase Request Awaiting Approval</h2><p>Hi {{ $json.approver.firstName }},</p><p><strong>{{ $json.requestor.name }}</strong> has submitted a purchase request requiring your approval.</p><ul><li><strong>Item:</strong> {{ $json.requestTitle }}</li><li><strong>Amount:</strong> {{ $json.requestAmount | currency }}</li><li><strong>Justification:</strong> {{ $json.justification }}</li></ul><p><a href='{{ $json.approvalUrl }}' style='background:#0f6cbd;color:#fff;padding:10px 20px;border-radius:5px;text-decoration:none;display:inline-block;'>Review & Approve</a></p>",
"Text": "Hi {{ $json.approver.firstName }}, {{ $json.requestor.name }} has submitted a purchase request for {{ $json.requestAmount }} ({{ $json.requestTitle }}). Review here: {{ $json.approvalUrl }}"
}
// After sending, log the message ID against the request record:
// {{ $output.notifyApprover.messageId }}