semos-agentura-email¶
Unified email client with LLM agent. Supports IMAP/SMTP, Outlook COM, CalDAV, and Microsoft Graph API backends.
Backends¶
| Backend | Calendar | Platform | Provider | |
|---|---|---|---|---|
| COM | Yes | Yes | Windows only | M365/Exchange |
| IMAP/SMTP | Yes | No | Any | Any |
| CalDAV | No | Yes | Any | Google, Nextcloud, iCloud |
| Graph API | No | No | Any | M365 (Phase 3) |
Email and calendar backends are independent (for example, IMAP for email plus CalDAV for calendar).
Auto-detection priority:
1. Explicit BACKEND=com|imap|graph overrides
2. IMAP_HOST + AZURE_CLIENT_ID + EMAIL_ADDRESS → IMAP
3. GRAPH_CLIENT_ID → Graph API
4. Windows → COM
Setup¶
Create a .env in this directory and configure the keys below.
IMAP/SMTP (any OS)¶
AZURE_CLIENT_ID=your-azure-app-client-id
AZURE_TENANT_ID=your-tenant-id
EMAIL_ADDRESS=user@example.com
IMAP_HOST=outlook.office365.com
SMTP_HOST=smtp.office365.com
COM (Windows, no config needed)¶
Outlook must be running. Auto-detected on Windows if no IMAP config is set.
@mailgent agent¶
MODEL=anthropic/claude-sonnet-4-5
AZURE_API_KEY=your-api-key
AZURE_API_BASE=https://your-endpoint.openai.azure.com
Agent discovery and runtime keys (AGENT_HOST, LOG_LEVEL, ROUTER_LLM_*) are documented in the
root README.
CLI¶
uv run python run.py # show help
# Email
uv run python run.py search-emails <query>
uv run python run.py read-email <query>
uv run python run.py read-email <query> --save-att
uv run python run.py draft <to> <subject> <body> [--cc=...] [--att=file1,file2]
uv run python run.py send <to> <subject> <body>
# Calendar
uv run python run.py events [days]
uv run python run.py free-slots [days]
uv run python run.py create-event <subject> <start> <end>
# @mailgent agent
uv run python run.py agent [interval] # polling loop (default 30s)
uv run python run.py agent-once # process pending and exit
# Archive
uv run python run.py archive # incremental dump to SQLite
uv run python run.py archive-stats
uv run python run.py archive-search <query>
@mailgent¶
Write @mailgent: in a draft or incoming email to trigger the LLM agent:
The agent processes the prompt, calls tools (search emails, check calendar, etc.), and inserts the response directly below the prompt with a unique ID:
Dear colleague,
@mailgent[a1b2c3d4]: What are my free slots next week?
------------ mailgent[a1b2c3d4] ------------
Monday: 10:00-12:00, 14:00-17:00
Tuesday: 8:00-10:00, 13:00-17:00
------------ mailgent[a1b2c3d4] ------------
Best regards
Re-run: Delete the response block and remove the [id] from the prompt.
Multiple prompts per email are supported, and each gets its own ID.
Tools available to @mailgent¶
search_emails: search by subjectread_email: read full email contentlist_events: calendar events for next N daysfree_slots: free meeting slotscreate_draft: create email draftdraft_reply/send_reply: reply to an emaildraft_event/send_event: create calendar eventset_flag: set an email's flag tomarked(follow-up),complete, ornone
Config¶
MAILGENT_TAG=@mailgent
MAILGENT_MODEL=anthropic/claude-sonnet-4-5 # or use MODEL=
MAILGENT_AUTO_REPLY=false # reply to inbox emails
MAILGENT_AUTO_SEND=false # send replies immediately
MAILGENT_TRUSTED_REPLY=*@company.com # auto-reply trust list
MAILGENT_TRUSTED_SEND=me@company.com # auto-send trust list
Tests¶
uv run pytest tests/ -v # all tests
uv run pytest tests/test_backend.py -v # unit tests (mocked, no backend needed)
uv run pytest tests/test_integration.py -v # integration tests (needs real backend)
Architecture¶
src/semos/agentura/email/
config.py Settings (Pydantic BaseSettings)
models.py EmailMessage, Attachment, EventInfo
exceptions.py Exception hierarchy
auth.py OAuth2 (MSAL device code flow)
imap_client.py IMAP read/search/draft
smtp_client.py SMTP send
client.py MailClient facade (IMAP + SMTP)
backend.py EmailBackend + CalendarBackend protocols, IMAPBackend, COMBackend, factory
com_client.py Outlook COM wrapper (Windows)
tools.py LLM tool definitions + executor
mailgent.py @mailgent agent loop
formatting.py Markdown ↔ plain/HTML conversion
archive.py Incremental SQLite archive