Skip to content
Skip to main content

Welcome users when they connect

Last updated:

A user has just handed your product access to their mailbox. That’s the moment they’re most likely to wonder what they agreed to, and the moment a clear email does the most good. A grant.created workflow sends one automatically.

This is also the cheapest workflow to build, because grant.created needs no configuration and fires exactly once per connection.

A welcome email confirming the account is connected and syncing, listing the account, provider, and the exact permissions granted

More than the other grant events. It’s the only one carrying the user’s display name and the list of scopes they approved, which is what makes a useful welcome email possible.

FieldExamplePresent on other grant events
email[email protected]yes
providergoogleyes
nameJohn Doeno
scope["gmail.readonly", "calendar"]no
grant_id<NYLAS_GRANT_ID>yes
login_id<LOGIN_ID>no
code25012yes, with a different value

Variables sit at the payload root, so it’s {{email}} and {{provider}}, not {{grant.email}}. There’s no booking_info wrapper and no recipient object, so use {{email}} for the account address and guard everything else.

It answers the question your support inbox gets. “Why does this app want access to my email” often arrives days after the connection, when the user has forgotten what they clicked.

Printing scope back to them turns that into a record they can check. It also gives your support team something to point at when a user claims they never granted calendar access.

{{#if scope}}{{#each scope}}{{this}}<br />{{/each}}{{else}}not reported{{/if}}

{{this}} is the current item inside {{#each}}, since scope is an array of strings rather than objects. The {{else}} branch covers a payload with no scope list.

Consider translating the raw scope strings into plain descriptions in your own copy. https://www.googleapis.com/auth/gmail.readonly is accurate and means nothing to a user, while “read your email, but not send or delete” is the sentence they need. Either keep the raw values as a reference list, as this template does, or map the scopes your product requests with eq inside the loop: {{#each scope}}{{#if (eq this "https://www.googleapis.com/auth/gmail.readonly")}}Read your email{{/if}}{{/each}}.

Create a template, then an application-level workflow. grant.created accepts either sender. Omit from and the welcome arrives from the user’s own address, which reads oddly for an onboarding email, so this is a good case for transactional send.

curl -X POST 'https://api.us.nylas.com/v3/workflows' \
-H "Authorization: Bearer $NYLAS_API_KEY" -H 'Content-Type: application/json' \
-d '{
"name": "Welcome, account connected",
"trigger_event": "grant.created",
"template_id": "<TEMPLATE_ID>",
"delay": 0,
"is_enabled": true,
"from": { "email": "[email protected]", "name": "Your Company" }
}'

Without from, the workflow sends through the grant that was just created. That mostly works here, since the grant is alive, but it means your welcome email arrives from the user’s own address, which reads oddly at best. A grant authenticated with identity-only scopes can’t send at all, and that failure is silent.

delay: 0 is right. The user is still in your product, and a welcome email that arrives an hour later has lost the context it depends on.

Should I use this to provision other things?

Section titled “Should I use this to provision other things?”

No. A workflow can only send email. If connecting an account should also create grant-level workflows, seed a database row, or start a sync, do that from a grant.created webhook in your own code.

Running both is fine and common: the webhook does the provisioning, the workflow sends the email. They’re independent, so a failure in your handler doesn’t stop the welcome going out.

Customize Scheduler email for each user covers the provisioning side, where grant.created is the hook that creates a new customer’s grant-level workflows.

This greets the user by name when the provider supplies one, falls back cleanly when it doesn’t, and lists the granted scopes.

The greeting guard is worth noting, because name is absent on providers that don’t return a profile:

{{#if name}}<p>Thanks, {{name}}.</p>{{else}}<p>Hi there,</p>{{/if}}

Render it with POST /v3/templates/render against a payload containing only grant_id as well as a full one. If the stripped version renders, the guards hold.