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 free/busy from calendar API
    const slots = await calendar.findFreeSlots({
      duration: 30,
      withinDays: 7,
      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];
      await calendar.createEvent({ ...slot, attendees: [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 top of the email API plus your calendar provider — no scheduling links, no Calendly redirects, just a conversational booking agent.