When your product creates meetings through the Events API and sends a Notetaker to them, the attendees have no idea the call is being captured. The calendar invite says nothing about it. An event.created workflow sends the disclosure with the invitation.
This is the Events API counterpart to the Scheduler version. The logic is the same and the template variables aren’t.

Why fire this from event.created?
Section titled “Why fire this from event.created?”Because it’s the only trigger in the chain that knows who is attending. The Notetaker triggers carry an event object containing just ical_uid, event_id, and master_event_id, with no participants and no title, so a template fired from notetaker.created has nobody to address.
event.created gives you participants, title, when, organizer, and conferencing at the payload root, and Nylas populates recipient.email per attendee. That’s everything a disclosure needs.
event.created fires whether or not a Notetaker was sent to that meeting, and workflows don’t filter, so scope the workflow to the accounts where recording is always on. That’s covered next.
How do I only send this for recorded meetings?
Section titled “How do I only send this for recorded meetings?”Use a grant-level workflow on the grants where every meeting is recorded, rather than an application-level workflow across everything. An application-level event.created workflow fires for every event on every calendar the application can see, including meetings your users create by hand, so a recording notice on all of them would be wrong as well as noisy.
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": "Recording notice", "trigger_event": "event.created", "template_id": "<TEMPLATE_ID>", "delay": 0, "is_enabled": true, "from": { "email": "[email protected]", "name": "Your Company" } }'If recording is a per-meeting choice rather than a per-account one, a workflow is the wrong tool. Consume the event.created webhook instead, check your own record of whether a Notetaker was invited, and send through transactional send when it was. That costs you a handler and gives you a check a workflow can’t make, because the event payload doesn’t say whether a Notetaker was invited.
Remember that grant-level workflows don’t appear in the Dashboard. Track which grants you have provisioned in your own system, because there is no interface that will tell you later.
What should the email say?
Section titled “What should the email say?”Name what’s captured, and say who to talk to. The template states audio, video, and a written transcript, because a Notetaker takes all three and attendees reading “recorded” tend to assume audio only.
Attendees can’t remove the Notetaker themselves, so point them at the organizer. The organizer’s address is in the payload as organizer.email, which makes that instruction concrete rather than generic.
Send it with delay: 0. A disclosure that arrives after the meeting has started is worthless, and events are often created minutes before they begin.
How does the template handle the payload?
Section titled “How does the template handle the payload?”Three things need care, and all three are guard problems rather than content problems.
The time depends on the kind of event. The when object takes one of three shapes, named by when.object:
when.object | Fields | Used for |
|---|---|---|
timespan | start_time, end_time, start_timezone, end_timezone | Timed meetings |
date | date | A single all-day event |
datespan | start_date, end_date | A multi-day all-day event |
Only timespan carries a Unix timestamp. formatDate throws on the date strings in date and datespan, so branch on when.object first and print the date as it arrives. The template prints only the start of a datespan, because Google and Microsoft set end_date to the day after the event ends.
For timed events, when.start_timezone comes from the event itself. Guard it anyway, because formatDate throws on a missing argument rather than defaulting.
{{#if (eq when.object "date")}} {{when.date}}, all day{{else}}{{#if (eq when.object "datespan")}} All day, starting {{when.start_date}}{{else}} {{#if when.start_timezone}} {{ formatDate when.start_time when.start_timezone "EEEE d LLLL yyyy, h:mm a ZZZZ" "en" }} {{else}} {{ formatDate when.start_time "UTC" "EEEE d LLLL yyyy, h:mm a ZZZZ" "en" }} {{/if}}{{/if}}{{/if}}The join link is three levels deep. conferencing is absent entirely on meetings without a video call, so each level needs its own guard.
{{#if conferencing}}{{#if conferencing.details}}{{#if conferencing.details.url}} <a href="{{conferencing.details.url}}">{{conferencing.details.url}}</a>{{/if}}{{/if}}{{/if}}Participants may have no name. Iterate with {{#each}} and guard the name inside the loop, so an attendee who was invited by address alone still appears.
{{#each participants}}{{#if name}}{{name}} {{/if}}<{{email}}>{{/each}}The complete template
Section titled “The complete template”This renders the email above for timed, all-day, and multi-day events, including events with no conferencing, no timezone, and unnamed participants.
<html>
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>This meeting will be recorded</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;">This meeting will be recorded</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>{{#if title}}<strong>{{title}}</strong>{{else}}Your upcoming meeting{{/if}} will be recorded and transcribed. A notetaker joins the call and captures audio, video, and a written transcript.</p><p>If you would rather it was not recorded, tell the organizer before the meeting starts.</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">Meeting</td><td class="detail-value">{{#if title}}{{title}}{{/if}}</td></tr> <tr><td class="detail-label">When</td><td class="detail-value">{{#if (eq when.object "date")}}{{when.date}}, all day{{else}}{{#if (eq when.object "datespan")}}All day, starting {{when.start_date}}{{else}}{{#if when.start_timezone}}{{ formatDate when.start_time when.start_timezone "EEEE d LLLL yyyy, h:mm a ZZZZ" "en" }}{{else}}{{ formatDate when.start_time "UTC" "EEEE d LLLL yyyy, h:mm a ZZZZ" "en" }}{{/if}}{{/if}}{{/if}}</td></tr> <tr><td class="detail-label">Join</td><td class="detail-value">{{#if conferencing}}{{#if conferencing.details}}{{#if conferencing.details.url}}<a href="{{conferencing.details.url}}" style="color: #0D6EFD;">{{conferencing.details.url}}</a>{{/if}}{{/if}}{{/if}}</td></tr> <tr><td class="detail-label">Who</td><td class="detail-value">{{#each participants}}{{#if name}}{{name}} {{/if}}<{{email}}>{{#unless @last}}<br />{{/unless}}{{/each}}</td></tr> </table> <div style="text-align: center; margin: 30px 0;"><a href="{{#if html_link}}{{html_link}}{{/if}}" style="background-color: #0D6EFD; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">View the meeting</a></div> <p style="font-size: 14px; color: #666666; text-align: center;">Recording begins when the meeting starts and stops when it ends.</p> </div>
<div class="footer"> <p>© 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}}Event: {{id}}{{/if}} </p> </div> </div></body>
</html>Render it with POST /v3/templates/render against a real payload before enabling the workflow. A compliance email that silently fails to send is worse than one that looks plain.
What’s next
Section titled “What’s next”- Tell Scheduler attendees the meeting is recorded for bookings made through Scheduler
- Email attendees when a meeting is booked for the plain confirmation on the same trigger
- Send the recording when Notetaker media is ready delivers the output afterwards
- Notetaker for how Notetaker joins meetings and what it captures