# Email a meeting summary after a call

Source: https://developer.nylas.com/docs/cookbook/notetaker/email-meeting-summary/

The notes from a sales call are only useful if they reach the room. Everyone says "I'll send a recap" and half the time it never goes out. If a bot already joined the meeting, recorded it, and produced a summary, the recap should send itself the moment the call ends.

This recipe wires that up. When a meeting bot finishes processing, a webhook fires, your code fetches the summary and transcript, and a recap email goes out through a connected mailbox, no human in the loop. It builds on the media fetch covered in [Get a meeting transcript and recording](/docs/cookbook/notetaker/get-transcript-and-recording/).

## How do you email a meeting summary automatically?

You email a recap in three steps: subscribe to the `notetaker.media` webhook so you know when processing finishes, fetch the meeting media with the [Notetaker API](/docs/v3/notetaker/) to get the summary and transcript, then send the recap through a connected mailbox. The whole chain runs server-side with no human action, and it works for any meeting a bot joined on Google Meet, Zoom, or Microsoft Teams.

Timing is the reason this is webhook-driven. Media isn't ready when the call ends, only after the bot finishes transcribing, usually 2 to 5 minutes later, so polling wastes calls. The webhook tells you the exact moment the summary exists.

## Trigger on the notetaker.media webhook

The `notetaker.media` webhook carries a `state` field, and it fires for more than one state, including `processing` and error states, not just success. Only `state` of `available` means the media is ready to download, so guard on it before you act. The payload also carries the notetaker `id` and the `grant_id`, the two values you need to fetch the media. Acknowledge with a `200 OK` within 10 seconds, then do the work.

The handler below captures the IDs and hands off to the send step. Acknowledging before fetching keeps the download and send, which can take a few seconds, from timing out the webhook window. Dedupe on the notetaker ID, because webhook delivery is at-least-once.

```python

from fastapi import FastAPI, Request, BackgroundTasks, HTTPException
app = FastAPI()

@app.post("/webhooks/notetaker")
async def on_media(request: Request, background_tasks: BackgroundTasks):
    raw = await request.body()
    # Reject forged events before acting (see Receive real-time webhooks for the check).
    if not verify_nylas_signature(raw, request.headers.get("X-Nylas-Signature")):
        raise HTTPException(status_code=401)
    payload = json.loads(raw)
    if payload["type"] == "notetaker.media":
        obj = payload["data"]["object"]
        if obj.get("state") == "available":       # also fires for processing/errors
            # Hand off everything, including the attendee lookup, to run after the 200 ack.
            background_tasks.add_task(send_recap, obj["grant_id"], obj["id"])
    return {"ok": True}
```

The webhook challenge handshake and signature check are the same as any Nylas webhook, covered in [Receive real-time webhooks](/docs/cookbook/use-cases/build/realtime-webhooks/).

## Fetch the summary and transcript links

With the IDs in hand, call `GET /v3/grants/{grant_id}/notetakers/{notetaker_id}/media` to get the meeting's media objects. The response returns a `summary` and a `transcript` object, each with a pre-authenticated `url`. Those links expire after one hour (`ttl` of 3,600 seconds), so download the content as part of the send rather than emailing the raw link.

The function below pulls the media and downloads the summary JSON. Downloading the file rather than forwarding the URL matters because a recipient who opens the email 90 minutes later would otherwise hit a dead link. The full media object and its retention behavior are documented in [Get a meeting transcript and recording](/docs/cookbook/notetaker/get-transcript-and-recording/).

```python

NYLAS = "https://api.us.nylas.com"
HEADERS = {"Authorization": f"Bearer {os.environ['NYLAS_API_KEY']}"}

def get_summary(grant_id, notetaker_id):
    media = requests.get(
        f"{NYLAS}/v3/grants/{grant_id}/notetakers/{notetaker_id}/media",
        headers=HEADERS).json()["data"]
    summary = requests.get(media["summary"]["url"]).json()  # link valid for 1 hour
    return summary
```

## Compose and send the recap email

Send the recap through the host's own mailbox with `POST /v3/grants/{grant_id}/messages/send`, the same endpoint used for any outbound mail. Because it sends as the host, the recap threads naturally and replies land back in their inbox. Drop the summary into the body and offer the transcript as a follow-up, so the email stays scannable in the 10 seconds most people give it.

The function below sends a formatted recap to the meeting attendees. Sending from the host's address rather than a no-reply alias is what gets the recap opened, since it looks like a normal message from a colleague. For the full send field reference, see [Send email without SMTP](/docs/cookbook/use-cases/build/send-email-without-smtp/).

```python
def send_recap(grant_id, notetaker_id):
    attendees = lookup_attendees(notetaker_id)  # from your own meeting record
    summary = get_summary(grant_id, notetaker_id)
    body = f"<p>{summary}</p><p>Reply here for the full transcript.</p>"
    requests.post(
        f"{NYLAS}/v3/grants/{grant_id}/messages/send",
        headers=HEADERS,
        json={
            "to": [{"email": a} for a in attendees],
            "subject": "Recap: today's meeting",
            "body": body,
        },
    ).raise_for_status()
```

## Send to attendees who aren't connected accounts

The host has a grant, but the prospect on the other end of the call doesn't, and you still want them to get the recap. Sending through the host's mailbox already reaches external attendees as ordinary recipients, so one `messages/send` covers the whole invite list. The host's per-account send limits apply, near 500 messages a day on Gmail consumer accounts and 2,000 on Workspace.

When no host mailbox should be on the email, for example a system-generated recap from your product, send from a verified domain instead. That path uses [transactional send](/docs/cookbook/email/transactional-send/), which needs no grant and no per-user mailbox, so the recap comes from your application rather than a person.

## Things to know about emailing recaps

The one-hour link expiry is the detail that bites people. The `url` on every media object is a signed link valid for 3,600 seconds, so a job that fetches now and sends the link for later delivery will ship a dead link. Download the summary and transcript content when the webhook fires, then attach or inline it.

Media only exists after processing completes, which is why this is webhook-driven rather than tied to the call's end time. A 30-minute call typically reaches `media_available` a few minutes after everyone hangs up. If you call the media endpoint while the media is still processing you get a `404`, and once the files are deleted past the retention window you get a `410`, so treat the webhook as the trigger and don't poll.

## What's next

- [Get a meeting transcript and recording](/docs/cookbook/notetaker/get-transcript-and-recording/) for the full media object and downloads
- [Send email without SMTP](/docs/cookbook/use-cases/build/send-email-without-smtp/) for the send request reference
- [Send transactional email from a domain](/docs/cookbook/email/transactional-send/) for recaps with no host mailbox
- [Receive real-time webhooks](/docs/cookbook/use-cases/build/realtime-webhooks/) for the webhook handshake and retries
- [Notetaker API](/docs/v3/notetaker/) for bot creation and meeting capture