Email Block
Send emails with Handlebars template support across multiple providers
Email Block
The Email block sends emails with full Handlebars template support, working seamlessly across Gmail, Outlook, Resend, and SendGrid.
Overview
Use the Email block when you need to:
- Send transactional emails (invoices, receipts, notifications)
- Create dynamic email content with templates
- Attach files to emails
- Schedule emails for later delivery
- Support multiple email providers with a unified interface
Prerequisites
| Provider | Connection Type | Requirements |
|---|---|---|
| Gmail | OAuth via Nango | google-mail provider configured |
| Outlook | OAuth via Nango | microsoft-graph provider configured |
| SendGrid | API Key via Nango | sendgrid provider configured |
| Resend | API Key | RESEND_API_KEY env var or api_key in config |
Configuration
Step Definition
{
"id": "send-invoice-email",
"type": "email",
"name": "Send Invoice Email",
"config": {
"action": "send",
"provider": "gmail",
"to": "{{input.customer_email}}",
"subject": "Invoice #{{input.invoice_number}} Ready",
"body": {
"html": "<p>Hello {{input.customer_name}},</p><p>Your invoice is ready.</p>"
}
}
}Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | send, sendTemplate, or schedule |
provider | string | Yes | gmail, outlook, resend, or sendgrid |
to | string/array | Yes | Recipient email address(es) |
subject | string | Yes | Email subject line |
body | object | Yes* | Email content (html and/or text) |
from | string | No | Sender address (provider-dependent) |
cc | array | No | CC recipients |
bcc | array | No | BCC recipients |
reply_to | string | No | Reply-to address |
attachments | array | No | File attachments |
template | object | No | Server-side template config (SendGrid) |
send_at | string | No | Schedule time (ISO 8601) |
Available Actions
send
Send an email immediately.
{
"id": "send-notification",
"type": "email",
"config": {
"action": "send",
"provider": "gmail",
"to": "{{input.recipient_email}}",
"cc": ["manager@company.com"],
"subject": "Order Confirmation - #{{input.order_id}}",
"body": {
"html": "<h1>Thank you for your order!</h1><p>Order #{{input.order_id}} has been confirmed.</p>",
"text": "Thank you for your order! Order #{{input.order_id}} has been confirmed."
},
"reply_to": "support@company.com"
}
}Output:
{
"id": "msg_abc123",
"threadId": "thread_xyz789",
"labelIds": ["SENT"]
}sendTemplate
Send using a server-side template (SendGrid only).
{
"id": "send-welcome",
"type": "email",
"config": {
"action": "sendTemplate",
"provider": "sendgrid",
"to": "{{input.user_email}}",
"template": {
"id": "d-abc123def456",
"variables": {
"first_name": "{{input.first_name}}",
"company_name": "{{input.company}}",
"login_url": "https://app.example.com/login"
}
}
}
}Parameters:
| Field | Type | Description |
|---|---|---|
template.id | string | SendGrid dynamic template ID |
template.variables | object | Variables passed to template |
schedule
Schedule an email for later delivery (Resend, SendGrid).
{
"id": "schedule-reminder",
"type": "email",
"config": {
"action": "schedule",
"provider": "resend",
"to": "{{input.user_email}}",
"subject": "Reminder: {{input.reminder_title}}",
"body": {
"html": "<p>This is your scheduled reminder for: {{input.reminder_title}}</p>"
},
"send_at": "{{input.reminder_time}}"
}
}Note: Gmail and Outlook do not support native email scheduling. For these providers, use Trigger.dev scheduled tasks instead.
Handlebars Templates
The Email block uses Handlebars for dynamic content rendering. Templates are processed before sending.
Basic Variables
<p>Hello {{input.customer_name}},</p>
<p>Your invoice #{{input.invoice_number}} for ${{input.amount}} is ready.</p>Conditionals
{{#if input.is_overdue}}
<p style="color: red; font-weight: bold;">
This invoice is overdue. Please pay immediately.
</p>
{{else}}
<p>Payment is due by {{input.due_date}}.</p>
{{/if}}Loops
<table>
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{{#each input.line_items}}
<tr>
<td>{{this.description}}</td>
<td>{{this.quantity}}</td>
<td>${{this.unit_price}}</td>
</tr>
{{/each}}
</tbody>
</table>
<p><strong>Total: ${{input.total}}</strong></p>Nested Properties
<p>Company: {{input.customer.company_name}}</p>
<p>Contact: {{input.customer.contact.first_name}} {{input.customer.contact.last_name}}</p>
<p>Address: {{input.customer.address.street}}, {{input.customer.address.city}}</p>Using Step Outputs
<p>Your order has been processed.</p>
<p>Transaction ID: {{steps.process-payment.output.transaction_id}}</p>
<p>Amount Charged: ${{steps.process-payment.output.amount}}</p>Attachments
Attach files to emails using base64-encoded content.
{
"id": "send-with-attachment",
"type": "email",
"config": {
"action": "send",
"provider": "gmail",
"to": "{{input.recipient}}",
"subject": "Monthly Report - {{input.month}}",
"body": {
"html": "<p>Please find the monthly report attached.</p>"
},
"attachments": [
{
"filename": "report-{{input.month}}.pdf",
"content": "{{input.pdf_base64}}",
"content_type": "application/pdf"
},
{
"filename": "data.csv",
"content": "{{steps.generate-csv.output.csv_base64}}",
"content_type": "text/csv"
}
]
}
}Attachment Fields:
| Field | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | Name of the attached file |
content | string | Yes | Base64-encoded file content |
content_type | string | No | MIME type (default: application/octet-stream) |
Provider-Specific Features
Gmail
- Uses RFC 2822 message format
- OAuth authentication via
google-mailprovider - Supports threading with
in_reply_toandreferences - No native scheduling support
{
"config": {
"provider": "gmail",
"in_reply_to": "{{input.original_message_id}}",
"references": ["{{input.thread_message_id}}"],
"headers": {
"X-Custom-Header": "custom-value"
}
}
}Outlook
- Uses Microsoft Graph API
- OAuth authentication via
microsoft-graphprovider - Supports priority levels
- No native scheduling support
{
"config": {
"provider": "outlook",
"priority": "1"
}
}Priority Values:
1or2= High importance3= Normal importance (default)4or5= Low importance
SendGrid
- Supports server-side dynamic templates
- Native email scheduling
- Open and click tracking
- API key authentication via
sendgridprovider
{
"config": {
"provider": "sendgrid",
"tracking": {
"opens": true,
"clicks": true
},
"send_at": "2024-12-25T09:00:00Z"
}
}Resend
- Simple, developer-friendly API
- Native email scheduling
- Direct API calls (no Nango)
- Requires
RESEND_API_KEYenv var orapi_keyin config
{
"config": {
"provider": "resend",
"api_key": "{{env.RESEND_API_KEY}}",
"from": "notifications@yourdomain.com",
"send_at": "2024-12-25T09:00:00Z"
}
}Note: Resend does not support server-side templates. Use inline Handlebars templates instead.
Template Variables Reference
| Variable | Description |
|---|---|
{{input.field}} | Workflow trigger input data |
{{steps.stepId.output.field}} | Output from previous step |
{{variables.name}} | Workflow-level variables |
{{env.VAR}} | Environment variables |
{{template_variables.field}} | Step-specific template variables |
Providing Additional Variables
Use template_variables to pass extra data for template rendering:
{
"config": {
"action": "send",
"provider": "gmail",
"to": "{{input.email}}",
"subject": "Welcome to {{company_name}}",
"body": {
"html": "<p>Welcome to {{company_name}}!</p><p>Your account: {{account_type}}</p>"
},
"template_variables": {
"company_name": "Acme Corp",
"account_type": "Premium"
}
}
}Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
connectionId is required | Missing OAuth connection | Ensure provider connection exists |
Failed to render template | Invalid Handlebars syntax | Check template for syntax errors |
Unsupported email provider | Invalid provider value | Use gmail, outlook, resend, or sendgrid |
template is required | Missing template for sendTemplate | Provide template.id for sendTemplate action |
send_at is required | Missing schedule time | Provide ISO 8601 datetime for schedule action |
Resend API error | Invalid API key or request | Check RESEND_API_KEY and request payload |
Provider-Specific Errors
Gmail:
{
"error": {
"code": 403,
"message": "Request had insufficient authentication scopes"
}
}SendGrid:
{
"errors": [
{
"message": "The from address does not match a verified Sender Identity",
"field": "from"
}
]
}Rate Limits
| Provider | Limit | Notes |
|---|---|---|
| Gmail | 100 emails/day (free), 2,000/day (Workspace) | Per user quota |
| Outlook | 30 messages/minute | Per mailbox |
| SendGrid | Varies by plan | 100/day (free), unlimited (paid) |
| Resend | 100/day (free), 50,000/month (Pro) | Based on plan |
Related Blocks
- Slack Block - Send Slack notifications
- Action Steps - Other integration actions
- Transform Steps - Data transformation
Example Workflows
Invoice Email Workflow
{
"name": "Send Invoice on Payment Due",
"trigger": {
"type": "webhook",
"provider": "quickbooks",
"events": ["invoice.created"]
},
"steps": [
{
"id": "get-customer",
"type": "action",
"action": "quickbooks.getCustomer",
"config": {
"customerId": "{{input.payload.CustomerRef.value}}"
}
},
{
"id": "send-invoice-email",
"type": "email",
"config": {
"action": "send",
"provider": "gmail",
"to": "{{steps.get-customer.output.PrimaryEmailAddr.Address}}",
"subject": "Invoice #{{input.payload.DocNumber}} from Acme Corp",
"body": {
"html": "<h2>Invoice #{{input.payload.DocNumber}}</h2><p>Dear {{steps.get-customer.output.DisplayName}},</p><p>Please find your invoice for <strong>${{input.payload.TotalAmt}}</strong>.</p><p>Due Date: {{input.payload.DueDate}}</p><p><a href='{{input.payload.InvoiceLink}}'>View Invoice</a></p><p>Thank you for your business!</p>"
},
"reply_to": "billing@acme.com"
}
},
{
"id": "notify-sales",
"type": "slack",
"config": {
"action": "sendMessage",
"channel": "#billing",
"text": "Invoice #{{input.payload.DocNumber}} sent to {{steps.get-customer.output.DisplayName}}"
}
}
]
}Welcome Email with Attachment
{
"name": "Send Welcome Email",
"trigger": {
"type": "webhook",
"provider": "hubspot",
"events": ["contact.creation"]
},
"steps": [
{
"id": "generate-welcome-pdf",
"type": "action",
"action": "documents.generatePdf",
"config": {
"template": "welcome-guide",
"data": {
"name": "{{input.properties.firstname}}",
"company": "{{input.properties.company}}"
}
}
},
{
"id": "send-welcome",
"type": "email",
"config": {
"action": "send",
"provider": "sendgrid",
"to": "{{input.properties.email}}",
"from": "welcome@company.com",
"subject": "Welcome to Company, {{input.properties.firstname}}!",
"body": {
"html": "<h1>Welcome, {{input.properties.firstname}}!</h1><p>We're excited to have you on board.</p><p>Please find your welcome guide attached.</p><p>If you have any questions, reply to this email.</p>"
},
"attachments": [
{
"filename": "welcome-guide.pdf",
"content": "{{steps.generate-welcome-pdf.output.pdf_base64}}",
"content_type": "application/pdf"
}
],
"tracking": {
"opens": true,
"clicks": true
}
}
}
]
}Scheduled Payment Reminder
{
"name": "Schedule Payment Reminder",
"trigger": {
"type": "api"
},
"steps": [
{
"id": "calculate-reminder-time",
"type": "transform",
"config": {
"operation": "map",
"data": {
"reminder_time": "{{input.due_date | dateAdd: -3, 'days'}}"
}
}
},
{
"id": "schedule-reminder",
"type": "email",
"config": {
"action": "schedule",
"provider": "resend",
"to": "{{input.customer_email}}",
"from": "reminders@company.com",
"subject": "Payment Reminder: Invoice #{{input.invoice_number}}",
"body": {
"html": "<p>Hello {{input.customer_name}},</p><p>This is a friendly reminder that invoice #{{input.invoice_number}} for ${{input.amount}} is due on {{input.due_date}}.</p><p><a href='{{input.payment_link}}'>Pay Now</a></p>"
},
"send_at": "{{steps.calculate-reminder-time.output.reminder_time}}"
}
}
]
}