Notetaker finishes processing minutes after a call ends, and until someone checks the API nobody knows the recording exists. A notetaker.media workflow pushes it out the moment it’s ready.

When does notetaker.media fire?
Section titled “When does notetaker.media fire?”When processing completes and the media is downloadable. The payload arrives with state set to available and a media object holding signed URLs for each artefact Notetaker produced.
"media": { "recording": "<SIGNED_URL>", "transcript": "<SIGNED_URL>", "summary": "<SIGNED_URL>", "thumbnail": "<SIGNED_URL>", "recording_duration": "1800", "recording_file_format": "mp4"}Which keys are present depends on what the session was configured to capture. A session with transcription off has no transcript, and one with video_recording off has no recording. Guard every one of them, because the renderer throws on a missing key and sends nothing.
Who receives this email?
Section titled “Who receives this email?”Not the meeting attendees. notetaker.media carries an event object with only ical_uid, event_id, and master_event_id, so there is no participant list to address. Send a test to confirm which address receives the message before you enable this for customers. A standalone Notetaker has no grant behind it at all.
If attendees need the recording, send it from your own code: take event.event_id from the payload, fetch the event to get its participants, and send through transactional send. A workflow can’t do that lookup.
How do I create the workflow?
Section titled “How do I create the workflow?”Point an application-level workflow at notetaker.media. Notetaker sessions are created deliberately, so the volume is low and an application-level workflow is safe here.
curl -X POST 'https://api.us.nylas.com/v3/workflows' \ -H "Authorization: Bearer $NYLAS_API_KEY" -H 'Content-Type: application/json' \ -d '{ "name": "Recording ready", "trigger_event": "notetaker.media", "template_id": "<TEMPLATE_ID>", "delay": 0, "is_enabled": true, "from": { "email": "[email protected]", "name": "Your Company" } }'Use delay: 0. The value of this email decays fast, and the links it carries have a limited life of their own.
What about the signed URLs expiring?
Section titled “What about the signed URLs expiring?”Say so in the email. The URLs in media are signed and time-limited, so a recipient who opens the message a week later finds dead links and assumes the recording is gone.
Two ways to handle it. The simple one is to tell people to download what they want to keep, which the template below does in its footer. The durable one is to point the button at a page in your own product that fetches a fresh URL on demand, using the Notetaker id from the payload:
<a href="https://yourapp.com/recordings/{{id}}">Watch the recording</a>That costs you a route and removes the expiry problem entirely. It also means you control who can open the recording, which a signed URL forwarded in an email doesn’t.
How does the template handle missing media?
Section titled “How does the template handle missing media?”Every media key is guarded separately, so a session that recorded audio but not video still produces a sensible email rather than failing to render.
{{#if media}}{{#if media.recording}} <a href="{{media.recording}}">Download the recording</a>{{else}}not available{{/if}}{{/if}}Both levels are needed. media itself is absent on a session that produced nothing at all, and reaching through it throws. The {{else}} branch matters as much as the guard: a row that silently disappears leaves the reader wondering whether the transcript failed or was never requested.
recording_duration is a string of seconds rather than a formatted length, so 1800 means 30 minutes. There’s no helper for converting it, so either print it as seconds, as the template does, or format it in your own product and link out.
The complete template
Section titled “The complete template”This renders the email above and handles a session missing any combination of recording, transcript, or duration.
<html>
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Your recording is ready</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 recording is ready</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 recording and transcript from your {{#if meeting_provider}}{{meeting_provider}} {{/if}}meeting are ready.</p>{{#if media}}{{#if media.recording_duration}}<p>Length: {{media.recording_duration}} seconds.</p>{{/if}}{{/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">Recording</td><td class="detail-value">{{#if media}}{{#if media.recording}}<a href="{{media.recording}}" style="color: #0D6EFD;">Download the recording</a>{{else}}not available{{/if}}{{/if}}</td></tr> <tr><td class="detail-label">Transcript</td><td class="detail-value">{{#if media}}{{#if media.transcript}}<a href="{{media.transcript}}" style="color: #0D6EFD;">Read the transcript</a>{{else}}not available{{/if}}{{/if}}</td></tr> <tr><td class="detail-label">Format</td><td class="detail-value">{{#if media}}{{#if media.recording_file_format}}{{media.recording_file_format}}{{/if}}{{/if}}</td></tr> <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> </table> <div style="text-align: center; margin: 30px 0;"><a href="{{#if media}}{{#if media.recording}}{{media.recording}}{{/if}}{{/if}}" style="background-color: #0D6EFD; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Watch the recording</a></div> <p style="font-size: 14px; color: #666666; text-align: center;">These links are signed and expire, so download anything you want to keep.</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}}Notetaker: {{id}}{{/if}} </p> </div> </div></body>
</html>Render it with POST /v3/templates/render against a payload with media absent as well as a full one. The Notetaker notification reference publishes the full payload shape.
What’s next
Section titled “What’s next”- Tell the host when Notetaker couldn’t join for the case where no media is ever produced
- Tell Scheduler attendees the meeting is recorded for the disclosure that should precede this
- Notetaker for session settings and what each one captures
- Notetaker notifications for the full
notetaker.mediapayload