# Using templates and workflows in Nylas

Source: https://developer.nylas.com/docs/v3/email/templates-workflows/

The Nylas [Templates](/docs/reference/api/application-level-templates/) and [Workflows](/docs/reference/api/application-level-workflows/) endpoints work together to enable custom message flows triggered by specific events.

## How templates and workflows work

> **Info:** 
> **You can create templates and workflows associated with either a Nylas application or a specific grant**. For the purposes of this documentation, we reference the application-level [Templates](/docs/reference/api/application-level-templates/) and [Workflows](/docs/reference/api/application-level-workflows/) endpoints.

> **Warn:** 
> **The Nylas SDKs do not currently include built-in support for the Templates and Workflows endpoints.** The examples below use cURL, Node.js (`fetch`), and Python (`requests`) to call the REST API directly. You can use any HTTP client in your preferred language to make these requests.

You can use the [Templates endpoints](/docs/reference/api/application-level-templates/) to create and manage reusable email templates with custom variables. When you [create a template](/docs/reference/api/application-level-templates/create-app-level-template/), you specify its content and the templating engine to use when rendering it.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/templates" \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --data '{
    "body": "<p>Hello, {{user.name}}. I'm testing templates from Nylas.</p>",
    "name": "Testing template",
    "subject": "Testing Nylas templates",
    "engine": "mustache"
  }'
```

```js [createTemplate-Node.js]
const response = await fetch("https://api.us.nylas.com/v3/templates", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <NYLAS_API_KEY>",
  },
  body: JSON.stringify({
    body: "<p>Hello, {{user.name}}. I'm testing templates from Nylas.</p>",
    name: "Testing template",
    subject: "Testing Nylas templates",
    engine: "mustache",
  }),
});

const template = await response.json();
console.log(template);
```

```python
response = requests.post(
    "https://api.us.nylas.com/v3/templates",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer <NYLAS_API_KEY>",
    },
    json={
        "body": "<p>Hello, {{user.name}}. I'm testing templates from Nylas.</p>",
        "name": "Testing template",
        "subject": "Testing Nylas templates",
        "engine": "mustache",
    },
)

template = response.json()
print(template)
```

```bash
nylas template create \
  --name "Testing template" \
  --subject "Testing Nylas templates" \
  --body "<p>Hello, {{user.name}}. I'm testing templates from Nylas.</p>"
```

```json [createTemplate-Response (JSON)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "app_id": "6c45fe5e-0bb6-41b9-9acc-ccb15bfc51eb",
    "body": "<p>Hello, {{user.name}}. I'm testing templates from Nylas.</p>",
    "created_at": 1640995200,
    "engine": "mustache",
    "id": "b79c82b2-a51b-4c54-8469-28006a43551a",
    "name": "Testing template",
    "object": "template",
    "subject": "Testing Nylas templates",
    "updated_at": 1640995200
  }
}
```

See [`nylas template create`](https://cli.nylas.com/docs/commands/template-create) for full options. Other template subcommands: [`list`](https://cli.nylas.com/docs/commands/template-list).


> **Info:** 
> **The [Nylas CLI](https://cli.nylas.com/) runs commands against your default grant.** Run [`nylas auth list`](https://cli.nylas.com/docs/commands/auth-list) to see your connected accounts and [`nylas auth switch <email>`](https://cli.nylas.com/docs/commands/auth-switch) to change which one commands run against. See the full [command reference](https://cli.nylas.com/docs/commands).


Nylas supports the following templating engines:

- [Handlebars](https://handlebarsjs.com/)
- [mustache }}](https://mustache.github.io/)
- [Nunjucks](https://mozilla.github.io/nunjucks/)
- [Twig](https://twig.symfony.com/)

After you create a template, you can pass its ID in a [Create Workflow request](/docs/reference/api/application-level-workflows/create-workflow/) to define the message the workflow sends.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/workflows" \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --data '{
    "delay": 5,
    "is_enabled": true,
    "name": "Test workflow",
    "template_id": "b79c82b2-a51b-4c54-8469-28006a43551a",
    "trigger_event": "message.created"
  }'
```

```js [createWorkflow-Node.js]
const response = await fetch("https://api.us.nylas.com/v3/workflows", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <NYLAS_API_KEY>",
  },
  body: JSON.stringify({
    delay: 5,
    is_enabled: true,
    name: "Test workflow",
    template_id: "b79c82b2-a51b-4c54-8469-28006a43551a",
    trigger_event: "message.created",
  }),
});

const workflow = await response.json();
console.log(workflow);
```

```python
response = requests.post(
    "https://api.us.nylas.com/v3/workflows",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer <NYLAS_API_KEY>",
    },
    json={
        "delay": 5,
        "is_enabled": True,
        "name": "Test workflow",
        "template_id": "b79c82b2-a51b-4c54-8469-28006a43551a",
        "trigger_event": "message.created",
    },
)

workflow = response.json()
print(workflow)
```

```bash
nylas workflow create \
  --name "Test workflow" \
  --template-id "b79c82b2-a51b-4c54-8469-28006a43551a" \
  --trigger-event "message.created"
```

```json [createWorkflow-Response (JSON)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "app_id": "6c45fe5e-0bb6-41b9-9acc-ccb15bfc51eb",
    "date_created": 1756477389,
    "delay": 5,
    "id": "b79c82b2-a51b-4c54-8469-28006a43551a",
    "is_enabled": true,
    "name": "Test workflow",
    "template_id": "b79c82b2-a51b-4c54-8469-28006a43551a",
    "trigger_event": "message.created"
  }
}
```

See [`nylas workflow create`](https://cli.nylas.com/docs/commands/workflow-create) for full options. Other workflow subcommands: [`list`](https://cli.nylas.com/docs/commands/workflow-list).

## Format dates and times in templates

When you [create a template](/docs/reference/api/application-level-templates/create-app-level-template/), you might want to show a date or time in it given a Unix timestamp. Nylas supports `formatDate`, a helper function for [Handlebars](https://handlebarsjs.com/), [Nunjucks](https://mozilla.github.io/nunjucks/), and [Twig](https://twig.symfony.com/) that renders dates and times from a Unix timestamp.

> **Warn:** 
> **[**mustache }}**](https://mustache.github.io/) doesn't support helper functions**. If you choose to use it to render your templates, it won't format Unix timestamps as a date or time.

`formatDate` accepts the following parameters:

| Parameter                               | Description                                                                                                                               | Default |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `timestamp` <font color="red">\*</font> | The time to render, in seconds using the Unix timestamp format.                                                                           | —       |
| `timezone`                              | The [IANA-formatted](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) time zone used to calculate the output.                | `"UTC"` |
| `format`                                | The output format. Supports the options described in the [Luxon library](https://moment.github.io/luxon/#/formatting?id=table-of-tokens). | `"fff"` |
| `locale`                                | The output language. Supports the language codes defined in [ISO 639](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes).      | `"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

`formatDate` follows the `{{ formatDate timestamp timezone format locale }}` format in [Handlebars](https://handlebarsjs.com/) templates.

```bash {6} [renderHandlebars-cURL]
curl --location 'https://api.us.nylas.com/v3/templates/render' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --data '{
    "body": "The event is on {{ formatDate event.ts event.tz null \"en\" }}.",
    "engine": "handlebars",
    "variables": {
      "event": {
        "ts": 1758978000,
        "tz": "America/Toronto"
      }
    }
  }'
```

```js [renderHandlebars-Node.js]
const response = await fetch("https://api.us.nylas.com/v3/templates/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <NYLAS_API_KEY>",
  },
  body: JSON.stringify({
    body: 'The event is on {{ formatDate event.ts event.tz null "en" }}.',
    engine: "handlebars",
    variables: {
      event: {
        ts: 1758978000,
        tz: "America/Toronto",
      },
    },
  }),
});

const rendered = await response.json();
console.log(rendered);
```

```python
response = requests.post(
    "https://api.us.nylas.com/v3/templates/render",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer <NYLAS_API_KEY>",
    },
    json={
        "body": 'The event is on {{ formatDate event.ts event.tz null "en" }}.',
        "engine": "handlebars",
        "variables": {
            "event": {
                "ts": 1758978000,
                "tz": "America/Toronto",
            },
        },
    },
)

rendered = response.json()
print(rendered)
```

```json [renderHandlebars-Response (JSON)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "body": "The event is on September 27, 2025 at 9:00 AM EDT."
  }
}
```

### Format dates and times in Nunjucks and Twig templates

`formatDate` follows the `{{ timestamp|formatDate(timezone, format, locale) }}` format in [Nunjucks](https://mozilla.github.io/nunjucks/) and [Twig](https://twig.symfony.com/) templates.

```bash {6} [renderNunjucks-cURL]
curl --location 'https://api.us.nylas.com/v3/templates/render' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --data '{
    "body": "The event is on {{ event.ts|formatDate(event.tz, null, \"en\") }}.",
    "engine": "nunjucks",
    "variables": {
      "event": {
        "ts": 1758978000,
        "tz": "America/Toronto"
      }
    }
  }'
```

```js [renderNunjucks-Node.js]
const response = await fetch("https://api.us.nylas.com/v3/templates/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <NYLAS_API_KEY>",
  },
  body: JSON.stringify({
    body: 'The event is on {{ event.ts|formatDate(event.tz, null, "en") }}.',
    engine: "nunjucks",
    variables: {
      event: {
        ts: 1758978000,
        tz: "America/Toronto",
      },
    },
  }),
});

const rendered = await response.json();
console.log(rendered);
```

```python
response = requests.post(
    "https://api.us.nylas.com/v3/templates/render",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer <NYLAS_API_KEY>",
    },
    json={
        "body": 'The event is on {{ event.ts|formatDate(event.tz, null, "en") }}.',
        "engine": "nunjucks",
        "variables": {
            "event": {
                "ts": 1758978000,
                "tz": "America/Toronto",
            },
        },
    },
)

rendered = response.json()
print(rendered)
```

```json [renderNunjucks-Response (JSON)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "body": "The event is on September 27, 2025 at 9:00 AM EDT."
  }
}
```

## Use notification data in template variables

Workflows support [all notifications](/docs/reference/notifications/) that Nylas sends. 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](/docs/reference/notifications/scheduler/booking-created/), you can access its information in your template.

```json
{
  "specversion": "1.0",
  "type": "booking.created",
  "source": "/nylas/passthru",
  "id": "<WEBHOOK_ID>",
  "time": 1725895310,
  "webhook_delivery_attempt": 1,
  "data": {
    "application_id": "<NYLAS_APPLICATION_ID>",
    "grant_id": "<NYLAS_GRANT_ID>",
    "object": {
      "booking_id": "<BOOKING_ID>",
      "booking_ref": "<BOOKING_REFERENCE>",
      "configuration_id": "<CONFIGURATION_ID>",
      "object": "booking",
      "booking_info": {
        "event_id": "<EVENT_ID>",
        "start_time": 1719842400,
        "end_time": 1719846000,
        "participants": [
          {
            "email": "leyah@example.com",
            "name": "Leyah Miller"
          },
          {
            "email": "nyla@example.com",
            "name": "Nyla"
          }
        ],
        "additional_fields": {
          "phone": "+1-415-555-0137",
          "company": "Nylas Inc."
        },
        "hide_cancellation_options": false,
        "hide_rescheduling_options": false,
        "participant_rescheduling_url": "https://example.com/reschedule/<BOOKING_REFERENCE>",
        "participant_cancellation_url": "https://example.com/cancel/<BOOKING_REFERENCE>",
        "host_language": "en",
        "guest_language": "en",
        "title": "Project sync with Leyah",
        "duration": 60,
        "location": "https://meet.google.com/abc-defg-hij",
        "organizer_timezone": "America/Toronto",
        "guest_timezone": "America/Toronto",
        "is_group_event": false,
        "organizer_calendar_id": "primary",
        "event_description": "Discuss Q3 roadmap and next steps",
        "event_html_link": "https://calendar.google.com/calendar/event?eid=abc123",
        "ical_uid": "040000008200E00074C5B7101A82E0080000000000000000000000000000000000",
        "host_confirmation_url": "https://example.com/confirm/<BOOKING_REFERENCE>"
      }
    }
  }
}
```

```html
Your meeting for <b>{{ booking_info.title }}</b> is scheduled on {{
booking_info.start_time|formatDate(booking_info.guest_timezone) }}. If you need
to reschedule,
<a href="{{ booking_info.participant_rescheduling_url }}">click here</a>.
```

### Personalize notifications

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.

### Notify individual participants

For trigger events that include a `participants` object (for example, [`booking.created` notifications](/docs/reference/notifications/scheduler/booking-created/)), 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. If you want to notify individual participants for [bookings](/docs/reference/api/bookings/) notifications, add `"notify_individually": "true"` to the `additional_fields` object.

```bash {6-8}
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": "nyla@example.com"
    },
    "participants": [],
    "start_time": 1709643600
  }'
```

To notify individual participants for [events](/docs/reference/api/events/) notifications, add `"notify_individually": "true"` to the `metadata` object.

```bash {10-12}
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": "leyah@example.com"
      },
      {
        "name": "Nyla",
        "email": "nyla@example.com"
      }
    ],
    "title": "Annual Philosophy Club Meeting",
    "when": {
      "start_time": 1674604800,
      "end_time": 1722382420,
      "start_timezone": "America/New_York",
      "end_timezone": "America/New_York"
    }
  }'
```

> **Success:** 
> **Looking for reusable email signatures?** Templates handle message body content, but for persistent per-user signatures that Nylas appends automatically at send time, see [Using email signatures](/docs/v3/email/signatures/).