Skip to content
Skip to main content

Customize Scheduler email for each user

Last updated:

One application-level workflow sends the same email to every user of your product. That’s the right answer for most teams. If you sell a white-label product, or your customers are agencies who send under their own name, you need a different email per customer, and that means grant-level workflows.

This recipe covers how to scope workflows and templates to a single grant, what it costs to operate one per customer, and the duplicate-email trap that catches people who mix the two levels.

Use them when the email has to differ per customer in ways a template variable can’t express: a different logo, sender name, tone, or language that belongs to your customer rather than to your product. Everything else is cheaper at the application level.

Application-levelGrant-level
EndpointPOST /v3/workflowsPOST /v3/grants/{grant_id}/workflows
Fires forEvery grant in the applicationOne grant
Objects to manage1 per trigger1 per trigger, per grant
Visible in the DashboardYesNo
ProvisioningOnce, by handOn every new grant, in code

The cost is real. Five booking triggers across 500 customers is 2,500 workflows to create, keep in sync, and remove when a grant is deleted. Before committing, check whether a variable solves it instead. Per-customer text in the email body, such as a company name or a support address, can be passed through the booking’s additional_fields and read in an application-level template with lookup, which keeps you at one workflow per trigger. The sender is different: from is a field on the workflow, not the booking, so a per-customer sender always needs a grant-level workflow.

Create a template scoped to the grant, then a workflow scoped to the same grant. Both use the /v3/grants/{grant_id}/ prefix, and the template comes back with grant_id set and app_id null.

curl -X POST "https://api.us.nylas.com/v3/grants/$GRANT_ID/templates" \
-H "Authorization: Bearer $NYLAS_API_KEY" -H 'Content-Type: application/json' \
-d '{
"name": "Acme booking confirmed",
"engine": "handlebars",
"subject": "Confirmed: {{booking_info.title}}",
"body": "<html>…</html>"
}'
curl -X POST "https://api.us.nylas.com/v3/grants/$GRANT_ID/workflows" \
-H "Authorization: Bearer $NYLAS_API_KEY" -H 'Content-Type: application/json' \
-d '{
"name": "Acme booking confirmed",
"trigger_event": "booking.created",
"template_id": "<GRANT_TEMPLATE_ID>",
"delay": 0,
"is_enabled": true,
"from": { "email": "[email protected]", "name": "Acme" }
}'

A grant-level workflow can reference either a grant-level template or an application-level one. That distinction is what gives you the middle option below.

There’s a third way to get per-customer senders without any of this. Omit from and each workflow sends through its own grant, so the message already arrives from that customer’s address with no domain registration and no per-customer sender to configure. That covers identity but not branding, so reach for grant-level workflows when the template itself has to differ, and for grant sending when only the address does.

Not necessarily. There are 3 shapes, and most teams want the middle one.

One template per customer. Maximum flexibility and maximum maintenance. A copy change means updating every template you have issued. Choose this only when customers genuinely author their own email.

One template per segment, many grants pointing at it. Create a handful of application-level templates, one per brand or tier, and point each customer’s grant-level workflow at the right one. You get per-customer senders and per-brand design while maintaining 3 or 4 templates instead of 500. The workflow carries the from address, so the sender still differs per customer even when the template is shared.

One shared template, per-customer values passed in. Keep a single application-level workflow and pass the customer’s details through the booking’s additional_fields, reading them with lookup. No grant-level objects at all. This works when the differences are body text, not layout or sender, and only for bookings that submit those fields. Declare them on the configuration’s scheduler.additional_fields, and if you book through the API, include them in every booking request.

The second shape is usually the right trade. Branding lives in a few templates, identity lives on the workflow, and the number of objects you maintain grows with your brand count rather than your customer count.

How do I provision workflows for new grants?

Section titled “How do I provision workflows for new grants?”

Create them from your own code when the grant appears. A workflow can’t create another workflow, so this is a webhook handler rather than a chained workflow.

Subscribe to grant.created as a webhook, and on delivery create the grant-level workflows for that grant. Mirror it with grant.deleted so workflows are removed when a customer disconnects, or you accumulate workflows pointing at grants that no longer exist.

# list what a grant currently has, for reconciliation
curl "https://api.us.nylas.com/v3/grants/$GRANT_ID/workflows" \
-H "Authorization: Bearer $NYLAS_API_KEY"

Reconcile on a schedule as well as on the webhook. Provisioning that runs only on grant.created drifts the first time a delivery is missed, and there is no interface that shows you the gap.

You have a workflow at both scopes on the same trigger. Application-level and grant-level workflows fire independently, so a grant covered by both sends 2 messages to the same person.

This is the most common failure when moving from one model to the other, and it’s hard to spot because the Dashboard lists only application-level workflows. A grant-level workflow created months ago keeps firing with nothing in the interface to show it exists.

List both scopes before debugging anything else:

curl "https://api.us.nylas.com/v3/workflows" \
-H "Authorization: Bearer $NYLAS_API_KEY"
curl "https://api.us.nylas.com/v3/grants/$GRANT_ID/workflows" \
-H "Authorization: Bearer $NYLAS_API_KEY"

When you migrate a customer from the application-level email to their own, disable the application-level workflow for everyone or accept that migrated customers get duplicates. There’s no per-grant exclusion on an application-level workflow.

The template model doesn’t change with scope. The greeting still needs both guard levels, times still resolve through the same fallback chain, and notify_individually still has to be declared on the Scheduler configuration before a booking can pass it. Customize Scheduler email for your app explains each of those, and the templates there work unmodified at the grant level.

Change the logo, colours, and sender for a customer, and leave the guards and the timezone chain alone. They’re the parts that stop the template failing silently on a payload that’s missing a field.