# Welcome users when they connect

Source: https://developer.nylas.com/docs/cookbook/workflows/welcome-connected-account/

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](/_images/workflows/grant-welcome.png)

## What does grant.created give me?

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.

| Field | Example | Present on other grant events |
| --- | --- | --- |
| `email` | `user@example.com` | yes |
| `provider` | `google` | yes |
| `name` | `John Doe` | no |
| `scope` | `["gmail.readonly", "calendar"]` | no |
| `grant_id` | `<NYLAS_GRANT_ID>` | yes |
| `login_id` | `<LOGIN_ID>` | no |
| `code` | `25012` | yes, 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.

## Why does listing the permissions matter?

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.

```handlebars
{{#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}}`.

## How do I create the workflow?

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.

```bash
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": "hello@yourdomain.com", "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?

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](/docs/reference/notifications/grants/grant-created/) 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](/docs/cookbook/workflows/scheduler-email-per-user/) covers the provisioning side, where `grant.created` is the hook that creates a new customer's grant-level workflows.

## The complete template

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

```html [welcomeTemplate-grant.created]

<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Your account is connected</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
        Helvetica, Arial, sans-serif;
      line-height: 1.6;
      color: #333333;
      margin: 0;
      padding: 0;
      background-color: #f9fbfe;
      font-size: 16px;
    }

    .container {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }

    .header {
      text-align: center;
      padding: 20px 0;
    }

    .content {
      background-color: #ffffff;
      padding: 30px;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }

    .detail-label {
      color: #666666;
      font-size: 14px;
      padding: 6px 16px 6px 0;
      vertical-align: top;
      white-space: nowrap;
    }

    .detail-value {
      padding: 6px 0;
      vertical-align: top;
    }

    .footer {
      text-align: center;
      padding: 20px 0;
      color: #666666;
      font-size: 14px;
    }

    @media only screen and (max-width: 600px) {
      .container {
        width: 100% !important;
        padding: 10px !important;
      }

      .content {
        padding: 20px !important;
      }
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="header">
      <img src="https://brand.nylas.com/assets/site_images/Nylas-Logo_Horizontal-Blue.png" alt="Nylas Logo"
        style="max-width: 150px;" />
    </div>

    <div class="content">
      <h1 style="margin-top: 0; color: #1A1A1A;">Your account is connected</h1>

      {{#if name}}<p>Thanks, {{name}}.</p>{{else}}<p>Hi there,</p>{{/if}}<p>{{#if email}}<strong>{{email}}</strong>{{else}}Your account{{/if}} is connected and syncing. Here is exactly what you granted access to.</p>

      <hr style="border: 0; border-top: 1px solid #eeeeee; margin: 20px 0;" />

      <h2 style="color: #4D4D4D; font-weight: 500;">Details</h2>

      <table cellpadding="0" cellspacing="0" border="0" style="width: 100%;">
        <tr><td class="detail-label">Account</td><td class="detail-value">{{#if email}}{{email}}{{else}}your connected account{{/if}}</td></tr>
        <tr><td class="detail-label">Provider</td><td class="detail-value">{{#if provider}}{{provider}}{{else}}not reported{{/if}}</td></tr>
        <tr><td class="detail-label">Permissions</td><td class="detail-value">{{#if scope}}{{#each scope}}{{this}}<br />{{/each}}{{else}}not reported{{/if}}</td></tr>
      </table>
      <div style="text-align: center; margin: 30px 0;"><a href="https://example.com/dashboard" style="background-color: #0D6EFD; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Open your dashboard</a></div>
      <p style="font-size: 14px; color: #666666; text-align: center;">You can disconnect at any time from your account settings.</p>
    </div>

    <div class="footer">
      <p>&copy; 2026 <a href="https://www.nylas.com/" style="color: #0D6EFD;">Nylas</a></p>
      <p style="font-size: 12px; color: #999999; margin-top: 4px;">
        {{#if grant_id}}Grant: {{grant_id}}{{/if}}
      </p>
    </div>
  </div>
</body>

</html>


```

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

```handlebars
{{#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.

## What's next

- [Reduce churn by catching inactive accounts](/docs/cookbook/workflows/reduce-churn-inactive-accounts/) is the other half of the account lifecycle
- [Customize Scheduler email for each user](/docs/cookbook/workflows/scheduler-email-per-user/) provisions grant-level workflows from this same event
- [Grant notifications](/docs/reference/notifications/grants/) for the full `grant.created` payload
- [Authentication](/docs/v3/auth/) for the OAuth flow that produces the grant