Build an AI scheduling assistant from your inbox

DIYAdvanced build

An AI assistant that classifies incoming emails, checks your calendar, and books meetings automatically. We built this, ran it in production, and then hit a wall. Here's what worked, what didn't, and how you'd finish it.

An honest note

This flow worked well with Google Calendar. When we moved to CalDAV (self-hosted calendar), the calendar integration broke and we never did the refactor. We're sharing it anyway because the architecture, the prompts, and the classification logic are the hard part — the calendar integration is a connector swap. If you're on Google Calendar, this works as-is.

What this flow does

Someone emails assistant@yourdomain.com with "Can we meet next Tuesday?" The AI reads the email, classifies the intent, checks your calendar for availability, and either books the meeting and confirms, or proposes alternative times. It handles new meetings, rescheduling, and cancellations.

No service gets access to your mailbox. No service gets permanent access to your calendar. The AI only sees the single email that was routed to it, and it only accesses calendar data at the moment it needs to check availability or create an event.

The architecture

This is a two-flow design. The first flow handles classification — figuring out what the email is actually asking for. The second flow handles execution — the actual calendar operations.

1

Email arrives

EmailConnect receives the message and webhooks the parsed JSON to n8n

2

Classification agent

LLM classifies intent: meeting request, information request, information sharing, or fallback

3

Route to handler

n8n routes to the appropriate sub-workflow based on classification

4

Meeting handler (sub-workflow)

Checks calendar, books/updates/cancels meetings, and drafts the reply

5

Reply sent

Confirmation, proposed times, or a "forwarded to human" message sent via SMTP

Flow 1: Email classification

The classification agent is the brain of the system. It looks at each incoming email and determines what kind of request it is, how confident it is, and what information might be missing.

The four categories

MEETING_REQUEST

Schedule, reschedule, or cancel a meeting

Triggers: "meet", "schedule", "available", "reschedule", "cancel", time references

INFORMATION_REQUEST

Asking for information, documents, procedures

Triggers: "Do you know", "Can you tell me", "Where can I find"

INFORMATION_SHARING

FYI updates, status reports, forwarded context

Triggers: "FYI", "Update on", "Thought you should know"

FALLBACK

Unclear intent, sensitive topics, needs human attention

Triggers: ambiguous requests, legal/financial topics, confidence < 7

Classification prompt

The classification agent outputs a structured JSON with the category, action type (for meetings), tone detection, urgency, and a confidence score. If confidence drops below 7, it defaults to fallback — better to escalate than to misclassify.

Classification output schema

{
  "category": "MEETING_REQUEST",
  "action_type": "schedule",
  "completeness": true,
  "tone": "informal",
  "urgency": "medium",
  "confidence": 9,
  "reasoning": "Sender explicitly asks to meet next week",
  "missing_info": null
}

The completeness field is key. If someone says "let's meet sometime" without proposing dates, the agent knows it needs to ask for time preferences before it can book anything. The tone field carries forward to the reply — a casual email gets a casual response.

View full classification prompt
You're an AI assistant listening to assistant@yourdomain.com.
Your first job is to classify incoming emails and determine
the appropriate response strategy.

## Email Details
Sender: {{ sender.name }} <{{ sender.email }}>
Subject: {{ subject }}
Message: {{ content.text }}

## Classification Rules
Analyze the email and classify it into ONE category:

1. MEETING_REQUEST - Schedule, change, or cancel a meeting
2. INFORMATION_REQUEST - Asking for information
3. INFORMATION_SHARING - Sharing information (FYI)
4. FALLBACK - Unclear, ambiguous, or sensitive

## Meeting Action Types (if MEETING_REQUEST):
- "schedule" - New meeting request
- "update" - Changing existing meeting
- "cancel" - Canceling or postponing

## Assessment Requirements
- completeness: Sufficient info to act? (true/false)
- tone: "formal" or "informal"
- urgency: "low", "medium", or "high"
- confidence: How confident? (1-10)

If confidence < 7, default to FALLBACK.

Always assume one-off meetings unless recurring was
explicitly mentioned.

Flow 2: Meeting handler

When the classifier identifies a meeting request, n8n triggers the meeting handler as a sub-workflow. It receives the original email plus the classification results (action type, tone, urgency, completeness).

What the meeting handler does

Schedule (new meeting)

Complete request: Checks calendar for the proposed times. If a slot works, books it directly. If not, proposes 2-3 alternatives on nearby days.

Incomplete request: Asks for missing details — preferred times, duration, topic — in a tone that matches the sender.

Update (reschedule)

Complete request: Finds the existing meeting on the calendar, checks availability for the new time, updates the event.

Incomplete request: Asks which meeting they mean (date/subject reference) and what needs to change.

Cancel

Complete request: Identifies and cancels the meeting.

Incomplete request: Asks which meeting to cancel.

Meeting handler defaults

ParameterDefault
Duration30 minutes
FormatVirtual (Google Meet)
Available hoursMon-Fri 9:30am - 4:30pm
TimezoneFrom calendar settings
TypeOne-off (unless explicitly recurring)
View full meeting handler prompt
You're an AI assistant. Handle all meeting-related requests
(scheduling, updates, cancellations) professionally and
efficiently.

## Context
Sender: {{ sender.name }} <{{ sender.email }}>
Subject: {{ subject }}
Message: {{ content.text }}

## Classification Results
Action Type: {{ assessment.action_type }}
Tone: {{ assessment.tone }}
Urgency: {{ assessment.urgency }}
Completeness: {{ assessment.completeness }}
Missing Info: {{ assessment.missing_info }}

## If action_type = "schedule" and complete:
1. Check calendar for requested timeframes
2. If a proposed slot fits, book directly
3. If no overlap, propose 2-3 alternatives on nearby days
4. Draft confirmation with meeting details
5. Default: 30 min, virtual (Google Meet), Mon-Fri 9:30-4:30

## If action_type = "schedule" and incomplete:
1. Request missing info in matching tone
2. Ask for: topic, preferred duration, 2-3 time slots,
   timezone, virtual vs physical

## If action_type = "update" and complete:
1. Find the specific meeting on calendar
2. Check availability for new proposed times
3. Update the meeting

## If action_type = "cancel" and complete:
1. Find and cancel the specific meeting

## Response Guidelines
- Match the sender's tone ({{ assessment.tone }})
- Don't mention other calendar items
- For high urgency, prioritize sooner slots
- Confirm specific meeting before updating/canceling
- The assistant won't attend the meeting

The calendar integration

The meeting handler uses n8n's tool-calling capability to give the LLM access to calendar operations: read events, create events, update events, delete events. With Google Calendar, n8n has built-in nodes that handle OAuth and provide these operations as tools the agent can call.

Works with Google Calendar

If you're using Google Calendar, the n8n Google Calendar nodes work well as agent tools. The agent can check availability, create events with Google Meet links, and handle updates/cancellations.

Where it got tricky: CalDAV

This flow ran in production with Google Calendar. It worked well — the agent could schedule, reschedule, and cancel meetings with natural language emails. Then we moved to a self-hosted CalDAV setup (for data sovereignty reasons, naturally), and the integration broke.

The problems with CalDAV in this context:

  • No native n8n nodes — Google Calendar has first-class n8n support. CalDAV requires custom HTTP request nodes with iCalendar (ICS) format handling.
  • ICS is verbose — creating and parsing .ics events in a Code node is more work than calling a Google Calendar API.
  • Free/busy queries — checking availability in CalDAV requires either FREEBUSY requests or fetching all events in a range and checking for conflicts. Both are more complex than Google's simple API.
  • Different server quirks — Nextcloud Calendar, Radicale, and Baikal all handle CalDAV slightly differently. What works on one may not work on another.

We haven't done the refactor yet. If you're on Google Calendar, the original approach works. If you're on CalDAV, the classification flow and prompts still work — you'd need to replace the calendar tool nodes with CalDAV HTTP requests.

Community challenge

If you build the CalDAV integration for this flow, we'd love to hear about it. The prompts and classification logic are the hard part — the calendar connector is a well-defined problem waiting for someone to solve it. Reach out at hello@emailconnect.eu.

Lessons learned

Two-flow architecture is worth it

Separating classification from execution keeps each flow focused. The classifier doesn't need calendar access, and the meeting handler doesn't need to worry about identifying email intent. Easier to debug, easier to extend.

Completeness detection saves awkward exchanges

The completeness field in the classification prevents the agent from trying to book a meeting when it doesn't have enough information. Without it, the agent would guess — and guessing wrong about meeting times is worse than asking.

Confidence thresholds prevent misclassification

The "if confidence < 7, default to fallback" rule is simple but effective. A misrouted email (booking a meeting when someone just wanted information) is far worse than escalating to a human.

Tone matching matters more than you'd think

A formal scheduling request getting a casual "sure, how about Tuesday?" response feels wrong. Carrying the tone assessment from classification to the reply draft makes the AI feel natural rather than robotic.

Calendar portability is an unsolved problem

The move from Google Calendar to CalDAV broke this flow. That's a real constraint to consider when building calendar integrations — if you might switch providers, abstract the calendar layer early.

The broader pattern

Even if you don't need a scheduling assistant, the classification → routing → handler pattern is reusable. The same architecture works for:

  • Customer support triage — classify by department, urgency, and topic, then route to different handler flows
  • Lead qualification — classify inbound inquiries as hot/warm/cold and route accordingly
  • Document processing — classify by document type (invoice, contract, report) and trigger different extraction flows

The key insight is that one LLM call for classification, followed by specialized handlers, works better than one monolithic agent trying to do everything. Each handler can have its own tools, its own prompt tuning, and its own error handling.

Build on this

The classification prompts and architecture described here are free to use and adapt. Start with a Google Calendar integration to get the core working, then swap connectors as needed.

Built the CalDAV version? Want to compare notes on the classification approach? Reach out at hello@emailconnect.eu.