Skip to main content

Email a meeting summary

This guide builds a complete serverless function that emails a formatted summary whenever a session produces one. It’s a great showcase of the three things that make functions powerful:

  • a named event handler that subscribes automatically,
  • the pre-authenticated ctx.cortex client,
  • an npm dependency (node-mailjet) and environment variables for credentials.

What it does

When a session summary is generated, Cortex emits a session.annotation.created event. Our function handles it, looks up the session to get its title, and sends the summary by email through Mailjet.

1. Add the dependency

In the function’s settings, declare the npm package:

await project.functions.updateDependencies({
dependencies: [{ name: "node-mailjet", version: "^6.0.0" }],
});

Once declared, its types are available in the editor and you can import it.

2. Add environment variables

Add your Mailjet API credentials as environment variables (encrypted at rest, injected into the runtime):

await project.functions.updateEnvVars({
envVars: [
{ key: "MJ_APIKEY_PUBLIC", value: "your-mailjet-public-key" },
{ key: "MJ_APIKEY_PRIVATE", value: "your-mailjet-private-key" },
],
});

Read them in your code with process.env.

3. Write the function

import { Client, SendEmailV3_1, LibraryResponse } from 'node-mailjet';

/**
* Cortex Serverless Function (TypeScript)
*
* Functions are written in TypeScript with full IntelliSense for events and context.
* A function can handle HTTP requests and/or Cortex events.
*/

// =============================================================================
// HTTP Handler (Required) — called for HTTP requests to your function URL
// =============================================================================
exports.handler = async (event: HttpEvent, ctx: HttpContext) => {
return {
statusCode: 200,
body: {
message: 'Hello from your function!',
timestamp: new Date().toISOString(),
},
};
};

// =============================================================================
// Event Handler — exporting on<Event> subscribes the function automatically.
// session.annotation.created → onSessionAnnotationCreated
// =============================================================================
exports.onSessionAnnotationCreated = async (
event: SessionAnnotationCreatedEvent,
ctx: EventContext,
) => {
console.log(JSON.stringify(event));

const mailjet = new Client({
apiKey: process.env.MJ_APIKEY_PUBLIC,
apiSecret: process.env.MJ_APIKEY_PRIVATE,
});

// ctx.cortex is a pre-authenticated, project-scoped Cortex client.
const session = await ctx.cortex.sessions.get(event.payload.data.sessionId);
console.log(JSON.stringify(session));

const data: SendEmailV3_1.Body = {
Messages: [
{
From: { Email: 'noreply@4players.io' },
To: [{ Email: 'pschuster@me.com' }],
Subject: 'Meeting Summary: ' + session.title,
HTMLPart: event.payload.data.content.shareableHtml as string,
TextPart: event.payload.data.content.sharablePlainText as string,
},
],
};

const result: LibraryResponse<SendEmailV3_1.Response> = await mailjet
.post('send', { version: 'v3.1' })
.request(data);

const { Status } = result.body.Messages[0];
console.log('Mailjet status:', Status);
};

A few things worth noting:

  • The handler name is the subscription. Exporting onSessionAnnotationCreated is all it takes to receive session.annotation.created events — there’s no separate event list.
  • event.payload.data is the bare event payload. For a session annotation that includes the sessionId and the plugin-defined content (here, shareableHtml / sharablePlainText produced by the summary).
  • ctx.cortex needs no setup — ctx.cortex.sessions.get(...) just works, and the returned object exposes its fields directly (e.g. session.title).

4. Publish and deploy

const fn = await project.functions.get(functionId);
await fn.publish({ changelog: "Email summary on session annotation" });
await fn.deploy();

On publish, Cortex transpiles your TypeScript, snapshots it as an immutable version, and detects that it subscribes to session.annotation.created. On deploy, it goes live on ODIN Fleet and starts receiving events.

Test it

Generate a summary on a session and the function fires:

const session = await project.sessions.get(sessionId);
await session.regenerateSummary(); // emits session.annotation.created

Check the function logs for the Mailjet status, and your inbox for the email.

Make handlers idempotent

Event delivery is durable and may retry, so a handler can run more than once for the same event. If duplicate emails would be a problem, dedupe on event.id.