Melian

Email

Overview

Melian manages multiple email accounts with full IMAP reading and SMTP sending. A tiered permission system controls autonomy per account, from fully autonomous replies to silent read-only monitoring. Accounts are polled on a configurable interval; unread messages flow through the attention pipeline for importance scoring. The dashboard surfaces any pending drafts awaiting approval.

Account Configuration

Each account is defined in config as an EmailAccountConfig. OAuth is used for Gmail; basic auth covers everything else. The check_interval field accepts cron expressions or durations like "30m".

export interface EmailAccountConfig {
  name: string;
  provider: "gmail" | "protonmail" | "generic";
  email: string;
  oauth?: {
    client_id: string;
    client_secret: string;
    refresh_token: string | null;
  };
  auth?: { user: string; pass: string };
  imap: { host: string; port: number; tls: boolean };
  smtp?: { host: string; port: number; tls: boolean };
  check_interval: string;
  send_enabled?: boolean;
}

Permission Tiers

export type PermissionTier = "autonomous" | "draft" | "blocked";
Tier Behavior
autonomous Melian composes and sends without approval
draft Melian creates a pending draft; you approve or discard in the dashboard
blocked Sending is rejected outright; read access only

Cached Email Shape

Emails are stored locally in SQLite after fetching. The importance field is populated by the attention pipeline classifier.

export interface CachedEmail {
  id: string;
  account: string;
  uid: number;
  from_address: string;
  from_name: string | null;
  subject: string | null;
  snippet: string | null;
  date: number;         // Unix timestamp
  is_read: number;      // 0 | 1
  importance: number | null;  // 0–3 from attention pipeline
}

Draft Workflow

When Melian wants to send a reply and the recipient's tier is draft, the message is written to a pending drafts table rather than transmitted. The dashboard shows all pending drafts with approve / discard actions. On approval the message is sent via SMTP; on discard it is deleted.

Permissions are per-recipient-address, not per-account. The check(toAddress) method checks the destination address against the autonomous and blocked address lists to determine whether a given send is allowed, requires approval, or is blocked.

Compose → permission check(toAddress)
  ├── autonomous  → send immediately
  ├── draft       → write to drafts table → user reviews in dashboard
  └── blocked     → reject, log reason

Tools

Tool Parameters Description
email_list_unread account?: string, limit?: number List unread messages, optionally filtered by account
email_search query: string, account?: string, limit?: number Full-text search across cached messages
email_read id: string Fetch and return full message body
email_check - Trigger an immediate IMAP poll on all accounts
email_send to: string, subject: string, body: string, account: string, in_reply_to?: string Send or draft a message (respects permission tier)
email_approve_draft id?: string Approve a pending draft and transmit it (uses most recent pending if omitted)
email_discard_draft id?: string, reason?: string Delete a pending draft without sending (uses most recent pending if omitted)

API Endpoints

Method Path Description
GET /email/accounts List configured accounts and their tiers
POST /email/check Trigger an immediate poll
GET /email/summary Unread counts and importance breakdown by account
GET /email/search?q=&account= Search cached messages
GET /email/drafts?status= List drafts (pending / approved / discarded)
POST /email/drafts/:id/approve Approve and send a draft
POST /email/drafts/:id/discard Discard a draft
GET /email/:id Fetch a single cached message
POST /email/send Send or draft a message via the API