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, and it can still show each customer their own logo. If you sell a white-label product, or your customers are agencies who send under their own name, you may need more than a logo: a different sender, layout, or language per customer. That means grant-level workflows.

This recipe covers per-customer logos with metadata, 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 sender, layout, tone, or language that belongs to your customer rather than to your product. A different logo or brand color doesn’t need them. Store it in a hidden field on the customer’s Scheduler configuration, as described in the next section. 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 hidden field on the configuration solves it instead. The sender can’t: from is a field on the workflow, not the booking, so a per-customer sender always needs a grant-level workflow.

Section titled “How do I give each customer their own logo?”

Store each customer’s logo URL in a hidden metadata field on their Scheduler configuration. The booking templates in Customize Scheduler email for your app read it from booking_info.additional_fields.logo_url and fall back to your default logo when it’s missing. One application-level workflow per trigger covers every customer, with no grant-level objects.

  1. Host the customer’s logo anywhere public, such as your own storage or the customer’s website. Nylas stores only the URL.

  2. When you create or update the customer’s configuration, add a logo_url field with type: "metadata" and the URL in default. Do this for each configuration the customer owns, because the value lives on the configuration, not the grant.

    curl -X PUT "https://api.us.nylas.com/v3/grants/$GRANT_ID/scheduling/configurations/$CONFIGURATION_ID" \
    -H "Authorization: Bearer $NYLAS_API_KEY" -H 'Content-Type: application/json' \
    -d '{
    "scheduler": {
    "additional_fields": {
    "notify_individually": {
    "label": "Notify individually",
    "type": "metadata",
    "required": false,
    "default": "true"
    },
    "logo_url": {
    "label": "Logo URL",
    "type": "metadata",
    "required": false,
    "default": "https://cdn.example.com/customers/acme/logo.png"
    }
    }
    }
    }'

    scheduler.additional_fields is replaced as a whole, so include every field the configuration already has.

  3. Use the booking templates unchanged. Each one checks for logo_url and shows your default logo in the {{else}} branches when it’s missing.

Bookings made on the customer’s scheduling page get their logo automatically. If your own code creates bookings through the Bookings API, pass logo_url in the request’s additional_fields, or the email shows your default. Group-event reminders also show the default, because they carry no additional_fields.

Each booking keeps the logo URL it was created with. If a customer changes their logo, update the file behind the same URL, or existing bookings keep showing the old one in reschedules and reminders. Brand colors and support addresses work the same way. Add a field per value and read it in the template with the same two-level guard. Use metadata in workflow templates covers which values are safe to store.

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. If only the logo or colors differ, use the third. If the sender or layout differs too, 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 on the configuration. Keep a single application-level workflow and store each customer’s logo, colors, or support address in hidden metadata fields on their configuration, as in How do I give each customer their own logo?. No grant-level objects at all. This works when the differences are values, not layout or sender.

The shapes combine. A grant-level workflow that points at a shared template still reads logo_url, so customers can have their own sender and their own logo while you maintain a handful of templates. Either way, 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.

The template already reads the customer’s logo from logo_url, so set that on the configuration rather than editing the template. Change colors and the sender per customer if you need to, 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.