Skip to content
Skip to main content

Tell Events API attendees a meeting is recorded

Last updated:

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.

A meeting notice email stating the meeting will be recorded and transcribed, with the start time, join link, and attendee list

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.

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.

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.objectFieldsUsed for
timespanstart_time, end_time, start_timezone, end_timezoneTimed meetings
datedateA single all-day event
datespanstart_date, end_dateA 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}}&lt;{{email}}&gt;{{/each}}

This renders the email above for timed, all-day, and multi-day events, including events with no conferencing, no timezone, and unnamed participants.

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.