# Tell the host when Notetaker can't join

Source: https://developer.nylas.com/docs/cookbook/workflows/notetaker-failed-to-join/

Notetaker joins meetings as a guest. If the meeting has a waiting room, or is limited to people in the organization, somebody has to let it in. When nobody does within 10 minutes of the join time it gives up, and without a notice the host finds out when they look for a recording that doesn't exist.

A `notetaker.meeting_state` workflow tells them while the meeting is still happening, when they can still fix it.

![An email telling the host the Notetaker couldn't join, explaining the waiting room cause, with the meeting link, status, and reason](/_images/workflows/notetaker-failed-join.png)

## When does `notetaker.meeting_state` fire?

On every change to the Notetaker's state during a meeting. The payload carries 2 fields that together say what happened: `status` holds one of 4 top-level states, and `meeting_state` holds one of 20 sub-states explaining the reason.

| `status` | Means |
| --- | --- |
| `connecting` | Joining the meeting |
| `attending` | In the meeting |
| `disconnected` | No longer in the meeting |
| `failed_entry` | Never got in |

`meeting_state` is where the reason lives, and the 20 values group into 5 kinds of outcome.

| Group | `meeting_state` values | Who can fix it |
| --- | --- | --- |
| Waiting or refused entry | `waiting_for_entry`, `entry_denied`, `admission_timeout`, `sign_in_required`, `meeting_capacity_reached` | The host, during the meeting |
| Bad link or code | `bad_meeting_code`, `bad_meeting_link`, `cannot_join` | Whoever scheduled it |
| Ran normally | `recording_active`, `meeting_ended`, `no_participants`, `no_activity` | Nobody, this is the happy path |
| Removed from the meeting | `kicked`, `api_request`, `cancelled` | Depends on who removed it |
| Something broke | `network_error`, `no_response`, `error`, `internal_error`, `unknown` | You, from the logs |

The first 2 groups are the ones worth emailing about, because somebody can act on them while the meeting is still running. `admission_timeout` and `waiting_for_entry` are the most common: Notetaker joins as a guest, so a waiting room or an org-only meeting needs somebody to admit it, and it gives up 10 minutes after the scheduled join time.

`sign_in_required` and `entry_denied` are worth separating in your copy. The first means the platform wants an account, the second means a human refused the bot, and the advice differs.

## How many of these will the workflow send?

One per state change. A workflow on `notetaker.meeting_state` sends on every transition, including `connecting` and `attending` on a meeting that went fine.

Two ways to handle that:

**Write the template to suit every state.** The template in this recipe branches on `status` and `meeting_state` with the `eq` helper, so each message describes what actually happened: joining, recording, left, or couldn't get in. This suits an internal or operations address, or a host who wants live updates.

**Use the webhook to send only on failures.** Subscribe to [`notetaker.meeting_state`](/docs/reference/notifications/notetaker/notetaker-meeting_state/), check `status` in your handler, and send only on `failed_entry` through [transactional send](/docs/v3/getting-started/transactional-send/) with the same template. Use this for customer-facing mail where one message per meeting is the right amount.

## What should the email say?

Explain the cause, because `failed_entry` and `admission_timeout` mean nothing to the person reading them. The host doesn't know Notetaker joins as a guest, and will assume the integration is broken rather than that a waiting room blocked it.

For a waiting-room failure, the template states three things: the recording doesn't exist, the Notetaker was waiting to be admitted, and it waited 10 minutes. The fix is for the host to admit the bot next time. A bad link or a sign-in requirement gets its own sentence, because the fix there is on the calendar event or the meeting settings instead.

Include the meeting link so the host can jump back in if the call is still running. `meeting_link` is in the payload, and on a 60-minute meeting a notice at minute 10 still leaves time to admit the bot and capture what remains.

## How do I create the workflow?

Point an application-level workflow at `notetaker.meeting_state`.

```bash
curl -X POST 'https://api.us.nylas.com/v3/workflows' \
  -H "Authorization: Bearer $NYLAS_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "name": "Notetaker join failure",
    "trigger_event": "notetaker.meeting_state",
    "template_id": "<TEMPLATE_ID>",
    "delay": 0,
    "is_enabled": true,
    "from": { "email": "recordings@yourdomain.com", "name": "Your Company" }
  }'
```

Use `delay: 0`. A notice about a meeting in progress is worthless five minutes late.

The `notetaker.meeting_state` payload has no participant list. Send a test to confirm which address receives the message before you enable this for customers.

## How does the template handle the payload?

The template picks its wording with `eq`, checking `status` first and then `meeting_state` for the detail. Handlebars `{{else if}}` keeps the chain flat:

```handlebars
{{#if (eq status "failed_entry")}}
  {{#if (eq meeting_state "bad_meeting_link")}}The meeting link didn't work.
  {{else if (eq meeting_state "entry_denied")}}Somebody declined to admit the Notetaker.
  {{else}}The Notetaker was waiting to be admitted.
  {{/if}}
{{else if (eq status "disconnected")}}
  The Notetaker has left the meeting.
{{else if (eq status "attending")}}
  The Notetaker is in the meeting and recording.
{{else}}
  The Notetaker is joining.
{{/if}}
```

`eq` on a missing top-level field is false rather than an error, so a transition without `meeting_state` falls through to the `{{else}}` branch. There's no `or` helper, so each value you want to handle gets its own `{{else if}}`.

The Details table prints `status` and `meeting_state` as they arrive, each in its own guard:

```handlebars
{{#if status}}{{status}}{{/if}}
{{#if meeting_state}}{{meeting_state}}{{/if}}
```

The raw values are there on purpose. `failed_entry` and `waiting_for_entry` are what a host will paste into a support conversation, and matching the string they saw to the string in your logs saves a round of questions.

`meeting_provider` is guarded inline so the sentence reads correctly whether or not it's present:

```handlebars
your {{#if meeting_provider}}{{meeting_provider}} {{/if}}meeting
```

With the provider it reads "your Google Meet meeting", without it "your meeting". The trailing space inside the guard is deliberate.

## The complete template

```html [notetakerFailed-notetaker.meeting_state]

<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Notetaker update</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">
      {{#if (eq status "failed_entry")}}
      <h1 style="margin-top: 0; color: #1A1A1A;">Your Notetaker couldn't join</h1>
      {{#if recipient}}{{#if recipient.first_name}}<p>Hi {{recipient.first_name}},</p>{{else}}<p>Hi there,</p>{{/if}}{{else}}<p>Hi there,</p>{{/if}}
      <p>The Notetaker couldn't get into your {{#if meeting_provider}}{{meeting_provider}} {{/if}}meeting, so there is no recording or transcript for it.</p>
      {{#if (eq meeting_state "bad_meeting_link")}}<p>The meeting link didn't work. Check the link on the calendar event and send the Notetaker again.</p>
      {{else if (eq meeting_state "bad_meeting_code")}}<p>The meeting code didn't work. Check the code on the calendar event and send the Notetaker again.</p>
      {{else if (eq meeting_state "sign_in_required")}}<p>The meeting only admits signed-in users. Allow guests to join, then send the Notetaker again.</p>
      {{else if (eq meeting_state "entry_denied")}}<p>Somebody in the meeting declined to admit the Notetaker.</p>
      {{else if (eq meeting_state "meeting_capacity_reached")}}<p>The meeting was full.</p>
      {{else}}<p>The Notetaker joins as a guest. If the meeting has a waiting room, or is limited to people in your organization, somebody has to admit it. It gives up 10 minutes after the scheduled join time.</p>
      {{/if}}
      {{else if (eq status "disconnected")}}
      <h1 style="margin-top: 0; color: #1A1A1A;">Your Notetaker left the meeting</h1>
      {{#if recipient}}{{#if recipient.first_name}}<p>Hi {{recipient.first_name}},</p>{{else}}<p>Hi there,</p>{{/if}}{{else}}<p>Hi there,</p>{{/if}}
      {{#if (eq meeting_state "kicked")}}<p>Somebody removed the Notetaker from your {{#if meeting_provider}}{{meeting_provider}} {{/if}}meeting. It recorded up to that point.</p>
      {{else if (eq meeting_state "meeting_ended")}}<p>The meeting ended and the Notetaker has left. The recording and transcript are being processed.</p>
      {{else}}<p>The Notetaker has left your {{#if meeting_provider}}{{meeting_provider}} {{/if}}meeting. The reason is below.</p>
      {{/if}}
      {{else if (eq status "attending")}}
      <h1 style="margin-top: 0; color: #1A1A1A;">Your Notetaker is in the meeting</h1>
      {{#if recipient}}{{#if recipient.first_name}}<p>Hi {{recipient.first_name}},</p>{{else}}<p>Hi there,</p>{{/if}}{{else}}<p>Hi there,</p>{{/if}}
      <p>The Notetaker is in your {{#if meeting_provider}}{{meeting_provider}} {{/if}}meeting and recording.</p>
      {{else}}
      <h1 style="margin-top: 0; color: #1A1A1A;">Your Notetaker is joining</h1>
      {{#if recipient}}{{#if recipient.first_name}}<p>Hi {{recipient.first_name}},</p>{{else}}<p>Hi there,</p>{{/if}}{{else}}<p>Hi there,</p>{{/if}}
      <p>The Notetaker is joining your {{#if meeting_provider}}{{meeting_provider}} {{/if}}meeting. If the meeting has a waiting room, admit it when it appears.</p>
      {{/if}}

      <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">Meeting</td><td class="detail-value">{{#if meeting_link}}<a href="{{meeting_link}}" style="color: #0D6EFD;">{{meeting_link}}</a>{{/if}}</td></tr>
        <tr><td class="detail-label">Status</td><td class="detail-value">{{#if status}}{{status}}{{/if}}</td></tr>
        <tr><td class="detail-label">Reason</td><td class="detail-value">{{#if meeting_state}}{{meeting_state}}{{/if}}</td></tr>
      </table>
      <div style="text-align: center; margin: 30px 0;"><a href="{{#if meeting_link}}{{meeting_link}}{{/if}}" style="background-color: #0D6EFD; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Open the meeting</a></div>
    </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 id}}Notetaker: {{id}}{{/if}}
      </p>
    </div>
  </div>
</body>

</html>


```

Render it with `POST /v3/templates/render` once for each `status` value, and once with only `id` present. [Notetaker troubleshooting](/docs/v3/notetaker/troubleshooting/) covers the join failures this email reports.

## What's next

- [Send the recording when Notetaker is ready](/docs/cookbook/workflows/notetaker-media-ready/) for the successful path
- [Notetaker troubleshooting](/docs/v3/notetaker/troubleshooting/) explains each join failure and how to prevent it
- [Why isn't my workflow sending email?](/docs/cookbook/workflows/workflow-not-sending/) for guard rules and render testing
- [Notetaker notifications](/docs/reference/notifications/notetaker/) for the full `notetaker.meeting_state` payload