Skip to main content

Courier

Fastmail-only Alpha preview. An AI-native email & calendar client over JMAP and CalDAV.

Your AI agent deserves its own email client — not an adapter wrapping a human one.

Not Courier Inc. / trycourier.com (notification API). This project's PyPI name is ai-courier. The CLI is courier; the MCP server command is courier-mcp.

Today Courier talks to Fastmail only. Gmail, iCloud, and Exchange are not supported — they are contribution targets, not a cold-install path. courier setup configures a Fastmail JMAP token plus a Fastmail CalDAV app password.

MCP write tools are gated by trust_level in ~/.config/courier/config.json (readfilecorrespondmanage). courier setup defaults new installs to file (read + archive/move/label/drafts). Existing configs that omit the field stay at manage so upgrades do not revoke send/trash. An agent cannot raise its own trust level.

The Problem

Every AI agent framework connects to email and calendar by wrapping human-facing apps. MCP servers adapt web UIs. Osascript automates native mail clients. Browser tools click through webmail. Each introduces friction because the agent is fighting an interface designed for someone else.

Courier takes a different approach: connect directly to the open protocols (JMAP for email, CalDAV for calendar) as a first-class client — a peer alongside your human apps, not a wrapper around them.

What Makes It Different

  • Context-window-optimized output — emails are structured for AI consumption, not HTML rendering
  • Session state & watermarks — "what's new?" is a first-class operation that persists between sessions
  • Triage classification — emails arrive pre-sorted: urgent, needs_reply, actionable, fyi, bulk
  • Batch-first operations — archive 15 emails in one call, not 15 separate requests
  • Real scheduling, not just storage — RSVPs send iTIP REPLY to the organizer; events with attendees send real invitations
  • Identity-aware sendingfrom on every write tool picks the matching Fastmail identity (alias) per call
  • Contacts as a first-class surface — full CRUD: lookup, search, create, update, delete (JMAP Contacts, RFC 9610/9553)
  • Sane timezone handling — TZID normalization, recurrence expansion, and input datetimes that land at the right instant (offsets honored; naive = account-local). courier setup asks for the IANA timezone.
  • Composable — higher-order operations like "find all emails from this person and summarize the thread"

Quick Start

pip install ai-courier        # or: uv tool install ai-courier

# Configure your Fastmail account
courier setup

# Check your inbox (triaged by priority)
courier inbox

# What's new since last check?
courier new

# Today's calendar
courier today

# Find free time
courier free

mcp 2.x: Courier 0.8.5+ pins mcp>=1.28,<2. Releases before 0.8.5 declared mcp>=1.0, so an unpinned uvx / pip install can pull mcp 2.x and crash courier-mcp on import (Server has no list_tools). Upgrade to 0.8.5+ (0.9.0 recommended), or pin the extra while you wait:

uvx --from ai-courier --with 'mcp>=1.12,<2' courier-mcp

As an MCP Server

Add to your Claude configuration:

{
  "mcpServers": {
    "courier": {
      "command": "courier-mcp"
    }
  }
}

list_tools only returns tools allowed at the configured trust_level. A file-tier agent never sees email_send. Calling a hidden tool still fails loudly with the required tier. See docs/THREAT_MODEL.md.

Available tools (full manage surface):

Email — read & triage

Tool Description
email_inbox Triaged inbox (urgent → bulk), 200-char snippets, saturation signal
email_new New emails since last check (watermark-based)
email_search Flexible search, including by mailbox (folder ID, name, or path)
email_sent_since Recent sent messages (queries Sent mailbox directly)
email_read Read full email by ID
email_thread Get full thread
email_thread_digest Quote-stripped, token-budgeted thread payload
email_drafts Authoritative Drafts listing with created_at / last_modified_at
email_attachments List attachments on an email
email_attachment_save Download an attachment to disk
email_mailboxes List folders/labels with IDs, paths, roles

Email — write & file

Tool Description
email_send Send email (plain or HTML; optional from identity; optional attachment_paths)
email_reply Threaded reply (In-Reply-To + References); draft or send; optional attachment_paths; replies to alias-addressed mail default to that alias
email_forward Forward an email, re-attaches original as .eml; draft or send
email_draft Create draft (optional attachment_paths)
email_archive Archive emails (batch; optional atomic label add/remove)
email_trash Trash emails (batch)
email_mark_read Mark read (batch)
email_move Move emails to a folder (by ID, name, or path)
email_label Add/remove labels without touching other memberships
list_identities Sending identities (aliases) available to from

Contacts

Tool Description
contact_lookup Full contact record by email address
contact_search Search contacts by name / text
contact_create Create a contact (name, emails, phones, org, title, notes)
contact_update Update a contact — name/org/title/notes replace; emails/phones append
contact_delete Delete a contact — permanent; there is no trash can for contacts

Calendar

Tool Description
calendar_today Today's events
calendar_week This week's events
calendar_events Events in date range
calendar_search Text search across summary / location / description
calendar_free Find free time slots (respects working hours)
calendar_free_both Free slots intersected across primary + delegate accounts
calendar_create Create event — attendees get real iTIP REQUEST invitations (notify_attendees=false to skip)
calendar_update Update event fields
calendar_move Move event between calendars
calendar_delete Delete event
calendar_rsvp Respond to an invite — sends iTIP REPLY to the organizer (notify_organizer=false to skip)

Status

Tool Description
courier_status Live connection probe, watermark status, server version

Customizing Triage Rules

Triage rules are defined in YAML. The defaults ship with Courier (see src/courier/default_rules.yaml). To customize, create ~/.config/courier/triage-rules.yaml:

# Force specific senders to a classification (checked first, confidence 1.0)
sender_overrides:
  "ceo@mycompany.com": { classification: urgent, reason: "VIP sender" }
  "deals@spammy.com": { classification: bulk, reason: "always bulk" }

# Custom rules — if you define any, they REPLACE the defaults entirely.
# Omit this section to keep defaults and only add sender overrides.
rules:
  - name: my-custom-rule
    classification: urgent
    confidence: 0.9
    reason: "keyword: '{match}'"
    subject_pattern: "\\b(fire|outage|p0)\\b"

Each rule supports regex match conditions (from_pattern, subject_pattern, body_pattern, domain_pattern) and guard conditions (requires_known_contact, requires_unknown_contact, max_size, requires_thread, requires_flagged). Rules are evaluated top-to-bottom; first match wins.

See src/courier/default_rules.yaml for the full schema documentation and all default rules.

As a Python Library

For in-process consumers (cron scripts, pipelines) that don't want to host an MCP server or shell out to the CLI:

from courier import Client

client = Client()                      # lazy connect, reads ~/.config/courier/config.json
for email in client.email_new():       # shares the MCP server's watermark
    if looks_routine(email):
        client.email_archive([email.id])

The library surface is deliberately narrow — read, label, archive, mark-read. No send, no calendar. Write methods honor trust_level (a read config cannot archive).

Architecture

AI Agent (Claude, GPT, etc.)
    │
    ▼
Courier MCP Server ← the novel layer
    ├── Email (JMAP)     ├── Calendar (CalDAV)    ├── Contacts (JMAP)
    │   ├── Watermarks   │   ├── TZID handling    │   ├── Lookup/search
    │   ├── Triage       │   ├── Recurrence       │   └── Create/update
    │   ├── Identities   │   ├── Free/busy
    │   └── Batch ops    │   └── iTIP REPLY/REQUEST
    └─────────────────────┘
    │
    ▼
SQLite (state, watermarks, contact signals)
    │
    ▼
JMAP API ──── CalDAV API
(Fastmail)    (Fastmail)

Courier talks to the same backend your human apps do. It's a parallel client, not a wrapper.

Requirements

  • Python 3.11+
  • A Fastmail account with two credentials (Fastmail issues these separately; courier setup prompts for both):
    • a JMAP API token (get one here) — scopes: Mail, and Contacts if you want the contact tools
    • a CalDAV app password — for the calendar tools

Other JMAP/CalDAV providers are on the roadmap (Gmail #2, iCloud #3, Exchange #1). Do not invent a second provider on speculation — see CONTRIBUTING.md.

Development

git clone https://github.com/iamdadzilla/courier.git
cd courier
pip install -e ".[dev]"
pytest

See CONTRIBUTING.md for tests and scope, SECURITY.md to report issues, CHANGELOG.md for release history, and docs/ROADMAP.md for the generated feature roadmap.

Why "Courier"?

A courier delivers messages directly. No intermediary, no adapter, no wrapper. Just the message.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ai_courier-0.9.1.tar.gz (290.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ai_courier-0.9.1-py3-none-any.whl (96.4 kB view details)

Uploaded Python 3

File details

Details for the file ai_courier-0.9.1.tar.gz.

File metadata

  • Download URL: ai_courier-0.9.1.tar.gz
  • Upload date:
  • Size: 290.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ai_courier-0.9.1.tar.gz
Algorithm Hash digest
SHA256 e1c7cae7d2e51a2f08c723f2e5a987d85cef86766e3d49c15b1a1c0af027446a
MD5 cbc3876cf204010893eedf41ea4fae2e
BLAKE2b-256 83f2fe498cb5d8f8d324d484fd871d2a8e5f8946c130ebc1bfa5837120f678bf

See more details on using hashes here.

Provenance

The following attestation bundles were made for ai_courier-0.9.1.tar.gz:

Publisher: publish.yml on iamdadzilla/courier

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ai_courier-0.9.1-py3-none-any.whl.

File metadata

  • Download URL: ai_courier-0.9.1-py3-none-any.whl
  • Upload date:
  • Size: 96.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ai_courier-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ab4a217b3618c6dde76bcc3410b146faf74c4183afa701e30f98cee0b0a6de73
MD5 352a32c3a7574a9eaa0988998bedc0b2
BLAKE2b-256 6300264b0dfee6c48f5c63fb7891230ee7cb76f460a56faf325a12605829e4c4

See more details on using hashes here.

Provenance

The following attestation bundles were made for ai_courier-0.9.1-py3-none-any.whl:

Publisher: publish.yml on iamdadzilla/courier

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.9.1 This release

2 files

0.9.0

2 files

0.8.5

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.5

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2.1

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.3

2 files

0.3.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page