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.