Skip to content
Skip to main content

Using templates and workflows in Nylas

Last updated:

The Nylas Templates and Workflows endpoints work together to enable custom message flows triggered by specific events.

Workflows are an outbound email tool, which is why they live alongside the Email API. A workflow’s only action is to send a message, so it watches for an event anywhere in Nylas and sends email to somebody when it happens. The event can come from Scheduler, Notetaker, the Events API, or the account lifecycle, but the workflow always responds the same way, by sending email.

How a workflow sends a templated email

A workflow sends an email automatically when an event happens, using a template you wrote. Create one in the Dashboard (Workflows) or with POST /v3/workflows. When the event happens, Nylas finds your enabled workflows for it, picks the recipients and sender, and fills in the template from the event's data. If the template uses a value the event doesn't have, nothing is sent. The Dashboard's System logs record every skip and failure.
Diagram as text
  • Trigger: Something happens: For example, someone books a meeting (booking.created), a user's connection expires (grant.expired), or a Notetaker recording is ready (notetaker.media).
  • Nylas: Find the workflows for this event: Nylas looks for enabled workflows set up for this event, both app-wide ones and ones for this specific account (grant).
  • Nylas: Decide who gets the email: For bookings, the booking's participants. For created or updated calendar events (event.created, event.updated), the event's participants. For everything else, the owner of the connected account.
    • One copy each: notify_individually: "true" in booking additional_fields or event metadata sends one copy per participant. Without it, one message goes to everyone.
    • No participants → Skipped: Nobody to send to. Logged in System logs.
  • Nylas: Decide who the email comes from: If the workflow sets a from address, the email comes from your verified domain. Otherwise it comes from the connected account's own mailbox.
    • No from, and the account can't send → Skipped: The connected account (grant) is invalid or doesn't have permission to send email.
    • Same event already handled → Skipped as a duplicate: If the same event arrives twice, each workflow sends only once.
  • Nylas: Fill in the template: Nylas replaces the template's placeholders with values from the event's data, plus recipient when there's a single recipient. Every placeholder must have a value (strict mode). (Now, or after delay minutes)
    • Fallbacks: {{#if}}…{{else}}: guard optional values such as metadata, one level at a time, with a fallback.
    • A placeholder has no value → Not sent: The template can't be filled in, so nothing goes out. System logs show the send as rejected.
  • Ends in one of:
    • Accepted → Email sent: System logs record the send.
    • Send refused → Send failed: The sending limit was reached or the send was rejected. System logs give the reason.

Application-level or grant-level workflow

A workflow can cover every connected account in your application, or just one. Start with an application-level workflow: one workflow per event covers every grant. Add grant-level workflows only when a customer needs their own branding or wording. Both levels fire for the same event, so a recipient can get 2 messages.
Diagram as text
  • Application-level workflow (Start here): Runs for every connected account (grant) in your application, so you set it up once. Create it in the Dashboard (Workflows) or with POST /v3/workflows.
    • Application-level template: One email template shared across your application. An application-level workflow can only use these. Create it in the Dashboard (Email templates) or with POST /v3/templates.
  • Grant-level workflow (Per-customer branding): Runs only for one connected account, so a customer can get their own version. You create one for each grant that needs it. Request: POST /v3/grants/{grant_id}/workflows.
    • Grant-level or application-level template: Use a template with that account's own wording, or reuse an application-level template. Request: POST /v3/grants/{grant_id}/templates for that grant's own template.
  • Rules for either level:
    • Both levels send: If an application-level and a grant-level workflow handle the same event, each one sends an email.
    • Who the email comes from: Without a from address, the email comes from the connected account's own mailbox. With from, it comes from your verified domain.
    • A custom sender needs an application-level template: A workflow that sets from can't use a grant-level template. The request fails with 400.
    • Expired or deleted accounts need your domain as sender: The account can no longer send, so grant.expired and grant.deleted workflows must send from your verified domain with from.

You can use the Templates endpoints to create and manage reusable email templates with custom variables. When you create a template, you specify its content and the templating engine to use when rendering it.

See nylas template create for full options. Other template subcommands: list.

Nylas supports the following templating engines:

After you create a template, you can pass its ID in a Create Workflow request to define the message the workflow sends.

See nylas workflow create for full options. Other workflow subcommands: list.

When you create a template, you might want to show a date or time in it given a Unix timestamp. Nylas supports formatDate, a helper function for Handlebars that renders dates and times from a Unix timestamp.

formatDate accepts the following parameters:

ParameterDescriptionDefault
timestamp *The time to render, in seconds using the Unix timestamp format.—
timezoneThe IANA-formatted time zone used to calculate the output."UTC"
formatThe output format. Supports the options described in the Luxon library."fff"
localeThe output language. Supports the language codes defined in ISO 639."en"

When you use formatDate, the ordering of its parameters matters. If you want to use only one optional parameter, you need to pass null for the other options. When a parameter is null, Nylas uses its default value.

Format dates and times in Handlebars templates

Section titled “Format dates and times in Handlebars templates”

formatDate follows the {{ formatDate timestamp timezone format locale }} format in Handlebars templates.

From one of 2 places, chosen by whether the workflow carries a from object.

Omit from and the workflow sends through the grant that triggered the event, so the message comes from that user’s own mailbox. It goes out through the provider’s own API, so it lands in their Sent folder, replies return to their inbox, and it’s subject to their provider’s per-mailbox sending limits. The grant needs mail send scopes. Without them, the workflow writes Workflow <ID> skipped: the grant is not valid or is missing the scopes required to send email to the Dashboard’s System logs.

Set from and the workflow sends through transactional send on a domain you registered with Nylas. The message carries your branding rather than a person’s name, works even when the grant is broken, and needs the domain verified for sending first. A workflow with from.email must use an application-level template: a create or update request that pairs it with a grant-level template returns 400 with a Workflow with a from email must use an application-level template.

Neither is the default answer. A Scheduler confirmation usually reads better from the host’s own address, while an account notification should come from your product. grant.expired and grant.deleted have no choice and require from, because the grant that triggered them can no longer send.

Choose before you create the workflow. Once a workflow has from, you can change the address but not remove it: a PUT with "from": null returns no fields provided to update. To move a workflow back to sending through the grant, create a new workflow without from and delete the old one.

Handlebars ships 6 built-in helpers, and Nylas adds formatDate, eq, and ne on top of them.

HelperDoesSource
if / unlessBranch on whether a value is setHandlebars
eachIterate an arrayHandlebars
withChange the current contextHandlebars
lookupRead a property by a key held in a variableHandlebars
logWrite to the render logHandlebars
formatDateRender a Unix timestamp in a timezone and localeNylas
eq / neTest whether two values are equal or not equalNylas

Use eq inside if to branch on a value, and chain cases with {{else if}}:

{{#if (eq booking_info.guest_language "es")}}
Tu reunión está confirmada.
{{else if (eq booking_info.guest_language "fr")}}
Votre réunion est confirmée.
{{else}}
Your meeting is confirmed.
{{/if}}

There’s no or or and, so each value gets its own branch. eq follows the same strict-mode rules as any other expression: comparing a missing top-level variable is false, but (eq parent.leaf "x") throws when parent is missing.

lookup reads a property whose key is held in another variable. It works as a nested expression, which is how a Scheduler template picks a value per recipient from the booking’s additional fields:

{{lookup booking_info.additional_fields recipient.email}}

That reads the additional field named after the recipient’s email address. An unmatched key renders as empty rather than throwing.

mustache supports no helpers at all, including formatDate. Set engine to handlebars explicitly on any template that formats a date or compares values.

A variable was missing. The renderer runs Handlebars in strict mode, so any variable absent from the payload throws, the render returns 400, and no message is sent. The request that caused the trigger still succeeds, so nothing in your own code reports a problem.

Standard Handlebars renders a missing value as an empty string. This renderer doesn’t, so check any template you port from another system.

ExpressionParent missingLeaf missing, parent present
{{parent.leaf}}throwsthrows
{{#if parent.leaf}}throwssafe
{{#if parent}}safen/a
{{ formatDate missing_ts }}throwsthrows

Guard one level at a time. A guard that reaches through a parent that doesn’t exist throws exactly like the unguarded expression:

{{#if recipient}}{{#if recipient.first_name}}Hi {{recipient.first_name}},{{else}}Hi there,{{/if}}{{else}}Hi there,{{/if}}

Empty strings are safe, and several payload fields arrive empty rather than absent. Test every template twice before enabling its workflow, once with a full payload and once with the optional fields removed. Why isn’t my workflow sending email? covers the remaining failure modes.

Use notification data in template variables

Section titled “Use notification data in template variables”

Workflows support 34 of the notification triggers that Nylas sends. The Workflows overview lists them. Triggers such as message.deleted and the message.transactional.* deliverability events aren’t supported. When an event triggers a workflow, Nylas automatically includes the information from its data object as a set of template variables. For example, if your project receives a booking.created notification, you can access its information in your template.

If you’re sending notifications to a single recipient, Nylas automatically passes their email address, first name, and last name to the template as a set of variables: recipient.email, recipient.first_name, and recipient.last_name respectively. You can reference these variables in your template to personalize the notification.

For trigger events that include a participants object (for example, booking.created notifications), you can set up your workflow to send a message to all participants at once, or each participant individually.

By default, Nylas sends messages to all participants at once. To notify individual participants for bookings notifications, set notify_individually to the string "true" in the booking’s additional_fields. Any other value, including the boolean true, sends one message to everyone.

Declare notify_individually on the configuration first, as a scheduler.additional_fields entry with type: "metadata" and default: "true". Bookings made on a scheduling page then get the value automatically. A booking request that passes an undeclared field returns 400 Additional field 'notify_individually' not found in configuration. If your own code creates bookings through the Bookings API, include the value in each request, as in the following example, because the API doesn’t read default.

curl --request POST \
--url "https://api.us.nylas.com/v3/scheduling/bookings?configuration_id=<SCHEDULER_CONFIG_ID>" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"additional_fields": {
"notify_individually": "true"
},
"end_time": 1709645400,
"guest": {
"name": "Nyla",
"email": "[email protected]"
},
"participants": [],
"start_time": 1709643600
}'

To notify individual participants for events notifications, add "notify_individually": "true" to the event’s metadata object. It applies to event.created and event.updated. event.deleted workflows send to the grant owner instead.

curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=<CALENDAR_ID>" \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <NYLAS_API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
"busy": true,
"description": "Come ready to talk philosophy!",
"location": "New York Public Library, Cave room",
"metadata": {
"notify_individually": "true"
},
"participants": [
{
"name": "Leyah Miller",
"email": "[email protected]"
},
{
"name": "Nyla",
"email": "[email protected]"
}
],
"title": "Annual Philosophy Club Meeting",
"when": {
"start_time": 1674604800,
"end_time": 1722382420,
"start_timezone": "America/New_York",
"end_timezone": "America/New_York"
}
}'

A workflow’s recipients depend on the trigger. Scheduler and event triggers send to the people on the booking or event. Every other trigger sends to the owner of the grant that triggered it.

TriggerRecipients
booking.*The booking’s booking_info.participants
event.created, event.updatedThe event’s participants
All other triggers, including event.deletedThe grant owner

Templates can read custom values from 2 places. Scheduler booking triggers carry the configuration’s hidden metadata fields in booking_info.additional_fields. Event triggers carry the event’s metadata object. Guard each value with {{#if}} and give it an {{else}} fallback, because strict mode fails the render when a value is missing. A custom value can change what the email says, but it can’t stop the workflow from sending. See Use metadata in workflow templates for a per-configuration logo example.