Build an AI scheduling assistant from your inbox

At the end you have an assistant address that classifies incoming emails with your own LLM, checks your calendar, and books, moves or cancels meetings, without any service reading your mailbox.

By Xander Groesbeek, founder · Published March 2026 · Updated October 2026 · 7 min read

In brief

What you'll build. An address such as assistant@yourdomain.com that reads "Can we meet next Tuesday?", checks your calendar, and books the meeting or proposes other times. It also handles rescheduling and cancellations.

What you need. An EmailConnect account (Free works), an n8n instance, an LLM API key, an SMTP account for replies, and Google Calendar (CalDAV works too, with extra effort).

How it works. EmailConnect posts each email to n8n as JSON. A classification flow decides what the email asks for; a meeting-handler flow then uses calendar tools to act on it and drafts the reply.

An honest note

This flow ran in production with Google Calendar. When we moved to a self-hosted CalDAV calendar, the calendar integration broke and we haven't done the refactor. We share it anyway: the architecture, the prompts and the classification logic are the hard part, and the calendar is a connector swap. On Google Calendar it works as is.

The steps at a glance

  1. 1

    Route an assistant address to n8n

    An EmailConnect alias that posts each email to an n8n webhook.

  2. 2

    Classify the email

    Flow 1: an LLM sorts it into one of four categories, with a confidence score.

  3. 3

    Handle meeting requests

    Flow 2: schedule, reschedule or cancel, and draft the reply.

  4. 4

    Connect your calendar

    Google Calendar nodes as agent tools; CalDAV takes more work.

No service gets access to your mailbox, and none gets standing access to your calendar. The LLM, which is yours, sees only the single email routed to it, and touches calendar data only when it checks availability or changes an event.

Step 1: Route an assistant address to n8n

This step gives you an address people can write to, with every email arriving in n8n as JSON.

Create an alias such as assistant@yourdomain.com (on your own domain with an MX record, or on a system address that works straight away) and set its webhook to the URL of an n8n Webhook node. See the getting started guide.

Both flows read three fields from the payload: message.sender (name and email), message.subject and message.content.text. In an n8n Webhook node they sit under body, for example $json.body.message.subject. Every field is listed in the payload reference.

Step 2: Classify the email (flow 1)

This step decides what the email is asking for, so the right handler acts on it and nothing is booked by mistake. The classification agent returns a category, how confident it is, and what information is missing.

CategoryMeaningTypical signals
MEETING_REQUESTSchedule, reschedule or cancel a meeting"meet", "schedule", "available", "reschedule", "cancel", time references
INFORMATION_REQUESTAsks for information, documents or procedures"Do you know", "Can you tell me", "Where can I find"
INFORMATION_SHARINGFYI updates, status reports, forwarded context"FYI", "Update on", "Thought you should know"
FALLBACKUnclear, sensitive, or needs a humanAmbiguous requests, legal or financial topics, confidence below 7

The agent returns structured JSON with the category, the action type for meetings, tone, urgency and a confidence score. Below a confidence of 7 it falls back to FALLBACK: better to pass an email to a human than to misroute it.

Classification output

{
  "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
}

completeness matters most. If someone writes "let's meet sometime" without proposing a time, the handler knows to ask for one before it books anything. tone is carried into the reply, so a casual email gets a casual answer.

View the 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: {{ message.sender.name }} <{{ message.sender.email }}>
Subject: {{ message.subject }}
Message: {{ 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.

Step 3: Handle meeting requests (flow 2)

This step does the actual scheduling. When the classifier returns MEETING_REQUEST, n8n runs the meeting handler as a sub-workflow, passing the original email and the classification (action type, tone, urgency, completeness).

ActionComplete requestIncomplete request
ScheduleChecks the proposed times; books one that fits, or proposes 2–3 alternatives on nearby daysAsks for the missing details (times, duration, topic) in the sender's tone
UpdateFinds the existing meeting, checks the new time, updates the eventAsks which meeting is meant and what should change
CancelFinds and cancels the meetingAsks which meeting to cancel

Defaults

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

## Context
Sender: {{ message.sender.name }} <{{ message.sender.email }}>
Subject: {{ message.subject }}
Message: {{ 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 reply (a confirmation, proposed times, or a note that a person will follow up) goes out by SMTP to message.sender.email.

Step 4: Connect your calendar

This step gives the meeting handler its hands: n8n's tool calling lets the LLM read, create, update and delete events. With Google Calendar, n8n's built-in nodes handle OAuth and work well as agent tools, including creating events with a Google Meet link.

Where it got tricky: CalDAV

After we moved to a self-hosted CalDAV calendar, the integration broke. The problems:

  • No native n8n nodes. CalDAV needs custom HTTP Request nodes and iCalendar (ICS) handling.
  • ICS is verbose. Creating and parsing .ics events in a Code node is more work than calling the Google Calendar API.
  • Free/busy queries. Checking availability takes a FREEBUSY request, or fetching every event in a range and checking for conflicts.
  • Server quirks. Nextcloud Calendar, Radicale and Baikal each handle CalDAV slightly differently.

On CalDAV the classification flow and prompts still work; replace the calendar tool nodes with CalDAV HTTP requests. If you build that, we'd like to hear about it: hello@emailconnect.eu.

Lessons learned

  • Two flows are worth it. The classifier needs no calendar access, and the handler doesn't have to work out intent. Each is easier to debug and extend.
  • Completeness detection avoids guessing. Without it the agent guesses meeting times, and guessing wrong is worse than asking.
  • A confidence threshold prevents misrouting. "Below 7, fall back" is simple, and booking a meeting for someone who wanted information is worse than asking a human.
  • Tone matching matters. A formal request answered with "sure, how about Tuesday?" feels wrong; carrying the tone into the reply fixes that.
  • Abstract the calendar early if you might switch providers; our move from Google to CalDAV is what broke this flow.

The pattern (one LLM call to classify, then a specialised handler per category) works beyond scheduling: support triage by department and urgency, lead qualification, or sorting documents into invoices, contracts and reports. Each handler gets its own tools, prompt and error handling.

How EmailConnect fits

  • No access to your mailbox. Only mail sent to the assistant address is processed. Privacy promise →
  • The AI is yours. EmailConnect delivers each email as JSON; the LLM that classifies it runs in your n8n workflow. There is an n8n node (n8n-nodes-emailconnect) as well as plain webhooks. Integrations →
  • Calendar invites arrive as attachments. An .ics file in an email comes in the payload like any other attachment, inline when it is small. Attachment processing →
  • Retries carry the same ID. A failed delivery is retried, and every attempt has the same webhook-id, so your workflow can skip an email it has already handled instead of booking twice. Webhook signing and IDs →
  • Processed only in the EU.Sub-processors →

Build your scheduling assistant

Create an assistant@ address, point it at your n8n webhook and send it a meeting request. The Free plan is enough to try it.

Related