Example: Calendar booking via email
Operations
When a prospect emails "let's chat next week", an agent reads the message, checks calendar availability, replies with three slot options, and books on confirmation.
// Triggered by a webhook on message.received OR a WebSocket subscriber
async function handleInboundReply(event: MessageReceivedEvent) {
// 1. Pull the full message
const msg = await mam(
"GET",
`/inboxes/${event.inbox_id}/messages/${event.message_id}`
);
// 2. LLM intent: is this a meeting request?
const intent = await llm.classify(msg.plain_body, [
"meeting_request", "thank_you", "decline", "more_info", "other",
]);
if (intent === "meeting_request") {
// 3. Pull busy intervals from the inbox's OWN calendar (built in — every
// inbox has one) and compute three open 30-min slots.
const { busy } = await mam(
"GET",
`/inboxes/${event.inbox_id}/calendar/free-busy`
);
const slots = openSlots(busy, { duration: 30, count: 3 });
// 4. Reply in-thread with the proposed slots
const replyBody = `Happy to chat. I have these times open:
${slots.map((s, i) => ` ${i + 1}. ${formatSlot(s)}`).join("\n")}
Reply with the number that works and I'll send a calendar invite.`;
await mam("POST", `/inboxes/${event.inbox_id}/reply/${event.message_id}`, {
plainBody: replyBody,
});
// 5. Remember which slots we offered, keyed by thread
await db.savePendingMeeting({
threadId: msg.threadId,
slots,
proposedAt: Date.now(),
});
return;
}
// 6. On their next reply ("number 2 works"), look up the pending slots
// by threadId, parse the number, create the calendar event, and confirm.
const pending = await db.findPendingMeetingByThread(msg.threadId);
if (pending) {
const choice = parseSlotChoice(msg.plain_body);
if (choice !== null) {
const slot = pending.slots[choice - 1];
// Create the event on the inbox's calendar with the prospect as an
// attendee — MyAgentMail sends them the calendar invite (.ics) by email.
await mam("POST", `/inboxes/${event.inbox_id}/calendar/events`, {
summary: "Intro call",
start: slot.start,
end: slot.end,
attendees: [{ email: extractEmail(msg.from) }],
});
await mam("POST", `/inboxes/${event.inbox_id}/reply/${event.message_id}`, {
plainBody: `Booked for ${formatSlot(slot)}. Calendar invite is in your inbox.`,
});
await db.deletePendingMeeting(pending.id);
}
}
}
The whole loop runs entirely on MyAgentMail — the email API and the inbox's built-in calendar (/inboxes/:id/calendar/*). No external calendar provider, no scheduling links, no Calendly redirects — just a conversational booking agent that hosts the meeting on its own calendar and emails the invite.