LoopFour

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

ProviderConnection TypeRequirements
GmailOAuth via Nangogoogle-mail provider configured
OutlookOAuth via Nangomicrosoft-graph provider configured
SendGridAPI Key via Nangosendgrid provider configured
ResendAPI KeyRESEND_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

FieldTypeRequiredDescription
actionstringYessend, sendTemplate, or schedule
providerstringYesgmail, outlook, resend, or sendgrid
tostring/arrayYesRecipient email address(es)
subjectstringYesEmail subject line
bodyobjectYes*Email content (html and/or text)
fromstringNoSender address (provider-dependent)
ccarrayNoCC recipients
bccarrayNoBCC recipients
reply_tostringNoReply-to address
attachmentsarrayNoFile attachments
templateobjectNoServer-side template config (SendGrid)
send_atstringNoSchedule 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:

FieldTypeDescription
template.idstringSendGrid dynamic template ID
template.variablesobjectVariables 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:

FieldTypeRequiredDescription
filenamestringYesName of the attached file
contentstringYesBase64-encoded file content
content_typestringNoMIME type (default: application/octet-stream)

Provider-Specific Features

Gmail

  • Uses RFC 2822 message format
  • OAuth authentication via google-mail provider
  • Supports threading with in_reply_to and references
  • 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-graph provider
  • Supports priority levels
  • No native scheduling support
{
  "config": {
    "provider": "outlook",
    "priority": "1"
  }
}

Priority Values:

  • 1 or 2 = High importance
  • 3 = Normal importance (default)
  • 4 or 5 = Low importance

SendGrid

  • Supports server-side dynamic templates
  • Native email scheduling
  • Open and click tracking
  • API key authentication via sendgrid provider
{
  "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_KEY env var or api_key in 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

VariableDescription
{{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

ErrorCauseSolution
connectionId is requiredMissing OAuth connectionEnsure provider connection exists
Failed to render templateInvalid Handlebars syntaxCheck template for syntax errors
Unsupported email providerInvalid provider valueUse gmail, outlook, resend, or sendgrid
template is requiredMissing template for sendTemplateProvide template.id for sendTemplate action
send_at is requiredMissing schedule timeProvide ISO 8601 datetime for schedule action
Resend API errorInvalid API key or requestCheck 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

ProviderLimitNotes
Gmail100 emails/day (free), 2,000/day (Workspace)Per user quota
Outlook30 messages/minutePer mailbox
SendGridVaries by plan100/day (free), unlimited (paid)
Resend100/day (free), 50,000/month (Pro)Based on plan

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}}"
      }
    }
  ]
}