Flow Studio
Email (SMTP / SendGrid)
Configuring the SendEmail node — recipients, subject, HTML body templates, attachments, and provider-specific credential resolution for SMTP and SendGrid.
Node Configuration
{
"nodeType": "SendEmail",
"name": "sendApprovalEmail",
"config": {
"credentialId": 7,
"provider": "smtp",
"from": "noreply@acme.com",
"to": "$output.fetchEmployee.email",
"cc": [],
"bcc": [],
"subject": "Invoice $output.createInvoice.invoiceNumber requires your approval",
"bodyHtml": "<h2>Hello {{$output.fetchEmployee.firstName}},</h2><p>Please review invoice ...</p>",
"bodyText": "Hello $output.fetchEmployee.firstName, please review invoice ...",
"attachments": [
{
"filename": "invoice-$output.createInvoice.invoiceNumber.pdf",
"contentBase64": "$output.generatePdf.base64Content",
"contentType": "application/pdf"
}
],
"retryCount": 2,
"retryDelayMs": 2000
}
}
Configuration Fields
| Field | Type | Description |
|---|---|---|
credentialId | int | SMTP password or SendGrid API key via ICredentialResolver |
provider | smtp | sendgrid | Email delivery provider. Determines which adapter implementation is used. |
from | string / expr | Sender address. SMTP: must match authenticated mailbox. SendGrid: must be a verified sender. |
to | string / string[] / expr | Recipient(s). Comma-separated string or array. Expressions evaluated per recipient. |
cc | string[] | CC recipients (optional). |
bcc | string[] | BCC recipients (optional). |
subject | string / expr | Email subject line. Expression syntax supported. |
bodyHtml | string / expr | HTML body. Template syntax: {{expression}} interpolation. |
bodyText | string / expr | Plain-text fallback body. |
attachments | array | Array of attachment objects with filename, contentBase64, contentType. |
Provider Differences
| Aspect | SMTP | SendGrid |
|---|---|---|
| Credential | SMTP password (STARTTLS) | SendGrid API key |
| Rate limiting | Server-side throttle | Plan-based daily limit |
| Tracking | No built-in | Opens, clicks, bounces via webhook |
| Attachments | Up to 25 MB total | Up to 30 MB total |
| Templating | BFAI expression engine | BFAI expression engine OR SendGrid dynamic templates |
EmailChannelAdapter
public class EmailChannelAdapter : IMessagingChannelAdapter
{
public string ChannelType => "Email";
public async Task<MessagingResult> SendAsync(
MessagingMessage message,
MessagingConfig config,
CancellationToken ct)
{
if (config.Provider == "sendgrid")
return await SendViaSendGrid(message, config, ct);
else
return await SendViaSmtp(message, config, ct);
}
private async Task<MessagingResult> SendViaSmtp(
MessagingMessage message, MessagingConfig config, CancellationToken ct)
{
var password = await _credentials.GetPasswordAsync(config.CredentialId, ct);
using var client = new SmtpClient();
await client.ConnectAsync(config.SmtpHost, config.SmtpPort, SecureSocketOptions.StartTls, ct);
await client.AuthenticateAsync(config.SmtpUsername, password, ct);
var email = new MimeMessage();
email.From.Add(MailboxAddress.Parse(config.From));
foreach (var recipient in config.To)
email.To.Add(MailboxAddress.Parse(recipient));
email.Subject = message.Subject;
var builder = new BodyBuilder { HtmlBody = message.BodyHtml, TextBody = message.BodyText };
foreach (var att in message.Attachments ?? [])
builder.Attachments.Add(att.Filename,
Convert.FromBase64String(att.ContentBase64),
ContentType.Parse(att.ContentType));
email.Body = builder.ToMessageBody();
await client.SendAsync(email, ct);
await client.DisconnectAsync(true, ct);
return new MessagingResult
{
MessageId = email.MessageId,
Channel = "email",
Recipient = string.Join(", ", config.To),
SentAt = DateTimeOffset.UtcNow
};
}
}
Multiple recipients: When
to is an expression returning an array (e.g., $output.fetchApprovers.emails), the executor sends one email with all addresses in the To field. To send individual emails per recipient, use a ForEach node wrapping a single-recipient SendEmail node.