@agenticmail/core 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +35 -0
- package/README.md +465 -0
- package/REFERENCE.md +1219 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/email-worker-template-BOJPKCVB.js +39 -0
- package/dist/index.d.ts +1365 -0
- package/dist/index.js +5816 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ope Olatunji (https://github.com/ope-olatunji)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
AgenticMail - Email infrastructure for AI agents.
|
|
26
|
+
|
|
27
|
+
Project: https://github.com/agenticmail/agenticmail
|
|
28
|
+
Author: Ope Olatunji
|
|
29
|
+
GitHub: https://github.com/ope-olatunji
|
|
30
|
+
|
|
31
|
+
This software includes components for programmatic email management,
|
|
32
|
+
SMTP/IMAP integration, MCP server tooling, Cloudflare gateway orchestration,
|
|
33
|
+
and OpenClaw plugin infrastructure. All packages within this monorepo
|
|
34
|
+
(@agenticmail/core, @agenticmail/api, @agenticmail/mcp, @agenticmail/openclaw,
|
|
35
|
+
and agenticmail) are covered under this license.
|
package/README.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# @agenticmail/core
|
|
2
|
+
|
|
3
|
+
Core SDK for [AgenticMail](https://github.com/agenticmail/agenticmail) — email infrastructure for AI agents.
|
|
4
|
+
|
|
5
|
+
This is the foundation layer that everything else builds on. If the API server, MCP server, OpenClaw plugin, and CLI are the ways people interact with AgenticMail, this package is what actually does the work underneath. It handles creating and managing AI agent accounts, sending and receiving real email, watching inboxes for new messages in real time, routing email to and from the internet (through Gmail relay or a custom domain with Cloudflare), filtering spam, scanning outgoing emails to prevent agents from leaking sensitive data, and storing everything in a local SQLite database.
|
|
6
|
+
|
|
7
|
+
Every other AgenticMail package depends on this one.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [What This Package Does](#what-this-package-does)
|
|
14
|
+
- [How Agents Work](#how-agents-work)
|
|
15
|
+
- [Sending and Receiving Email](#sending-and-receiving-email)
|
|
16
|
+
- [Real-Time Inbox Watching](#real-time-inbox-watching)
|
|
17
|
+
- [Internet Email (Gateway)](#internet-email-gateway)
|
|
18
|
+
- [Keeping Things Safe](#keeping-things-safe)
|
|
19
|
+
- [Email Approval Workflow](#email-approval-workflow)
|
|
20
|
+
- [Spam Protection](#spam-protection)
|
|
21
|
+
- [Email Sanitization](#email-sanitization)
|
|
22
|
+
- [Search](#search)
|
|
23
|
+
- [Data Storage](#data-storage)
|
|
24
|
+
- [Setup and Dependencies](#setup-and-dependencies)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## What This Package Does
|
|
30
|
+
|
|
31
|
+
AgenticMail Core provides 15 major components organized into modules:
|
|
32
|
+
|
|
33
|
+
### Agent Management
|
|
34
|
+
- **AccountManager** — Creates, lists, finds, and deletes AI agent accounts. Each agent gets their own email address, login credentials, and a unique API key. Agent names must be email-safe (letters, numbers, dots, hyphens, underscores only).
|
|
35
|
+
- **AgentDeletionService** — When you delete an agent, this service first archives every email from their inbox, sent folder, and any custom folders into a JSON file and a database record. It produces a detailed deletion report with statistics: total emails archived, date range, top correspondents, and which folders had messages. The report is saved to `~/.agenticmail/deletions/` and the database. At least one agent must always exist — the system prevents deleting the last one.
|
|
36
|
+
|
|
37
|
+
### Email Operations
|
|
38
|
+
- **MailSender** — Sends email through the local Stalwart mail server using SMTP. Supports plain text bodies, HTML bodies, file attachments, CC/BCC recipients, reply-to addresses, and proper email threading headers (In-Reply-To and References) so replies show up correctly in email clients. You can also set a display name that appears in the "From" field (e.g., "Research Assistant" instead of just the email address). Every sent email returns the raw RFC822 message bytes so it can be copied to the Sent folder.
|
|
39
|
+
- **MailReceiver** — Reads email from the local Stalwart mail server using IMAP. Can list messages (newest first, with pagination), fetch the full content of a specific email, search by sender/subject/body/date/read-status, move messages between folders, create new folders, mark messages as read or unread, delete messages, and fetch multiple messages at once. Tracks connection state carefully and guards against stale connections.
|
|
40
|
+
- **parseEmail** — Takes a raw email (the RFC822 format that mail servers use internally) and turns it into a structured object with separate fields for subject, sender, recipients, body text, HTML, attachments, and headers. Handles relay emails specially — when an email was forwarded through Gmail/Outlook relay, it detects the `X-Original-From` header and uses the real external sender address instead of the local `@localhost` address.
|
|
41
|
+
|
|
42
|
+
### Inbox Monitoring
|
|
43
|
+
- **InboxWatcher** — Monitors an agent's inbox in real time using IMAP IDLE (a protocol feature where the mail server pushes notifications instead of the client polling). When a new email arrives, it fires a "new" event with the parsed email content. When an email is deleted, it fires an "expunge" event. When flags change (like read/unread status), it fires a "flags" event. The watcher creates a fresh IMAP connection each time it starts and holds a mailbox lock for the duration, which is required for IMAP IDLE to work.
|
|
44
|
+
|
|
45
|
+
### Internet Email Gateway
|
|
46
|
+
- **GatewayManager** — The central orchestrator for sending and receiving real internet email (not just local agent-to-agent email). Supports two modes: relay mode (uses your existing Gmail or Outlook as a middleman) and domain mode (your own domain with Cloudflare handling DNS, tunnels, and email routing). Automatically figures out whether an email is going to another local agent or to the outside world, and routes it through the right path. Handles inbound email delivery from relay polling or the Cloudflare webhook, runs spam filtering on incoming messages, and manages the blocked email approval workflow (including detecting when the owner replies "approve" or "reject" to the notification email).
|
|
47
|
+
- **RelayGateway** — Handles the Gmail/Outlook relay mode specifically. For outbound email, it sends through Gmail/Outlook SMTP using sub-addressing (e.g., `you+agentname@gmail.com`) so replies route back to the right agent. For inbound email, it polls the Gmail/Outlook IMAP account every 30 seconds looking for new messages. Uses exponential backoff on failures (30 seconds → 1 minute → 2 minutes, capping at 5 minutes) and never permanently stops polling — it always reschedules. On the first poll, it scans the 50 most recent messages; after that, it only looks at new arrivals. Tracks which messages have been delivered to prevent duplicates.
|
|
48
|
+
- **CloudflareClient** — API client for Cloudflare services. Can manage DNS zones, create/delete DNS records, create and manage Cloudflare Tunnels, deploy Cloudflare Workers (the Email Worker that receives inbound email), enable/disable Email Routing, set catch-all rules, search for available domains, and check domain registration status.
|
|
49
|
+
- **TunnelManager** — Manages the Cloudflare Tunnel lifecycle. Downloads the `cloudflared` binary if needed (platform-specific for macOS and Linux, both ARM and Intel), creates tunnels via the Cloudflare API, starts the tunnel process, configures ingress rules (routing web traffic to the API server and email traffic to Stalwart), and stops tunnels cleanly. The tunnel token is passed via environment variable rather than command-line argument so it doesn't show up in process listings.
|
|
50
|
+
- **DNSConfigurator** — Automatically creates all the DNS records needed for email to work on a custom domain: MX records (for receiving), SPF record (proving you're authorized to send), DKIM TXT record (cryptographic signature verification), and DMARC record (policy for handling authentication failures). Also creates CNAME records for the Cloudflare Tunnel. Before making changes, it detects and removes conflicting records, but preserves Cloudflare Email Routing's own managed MX records. Can detect the server's public IP address automatically.
|
|
51
|
+
- **DomainPurchaser** — Searches for available domain names across multiple TLDs (.com, .net, .io, .dev) and checks availability. Note that Cloudflare's API doesn't support programmatic domain purchase with API tokens, so actual purchases must be done through the Cloudflare dashboard or another registrar.
|
|
52
|
+
- **RelayBridge** — A small local HTTP-to-SMTP bridge that runs on localhost. Used in domain mode so that Cloudflare Workers (which can't connect to SMTP ports directly) can submit outbound email through an HTTP endpoint that then relays it to Stalwart for DKIM signing and delivery.
|
|
53
|
+
|
|
54
|
+
### Mail Server Administration
|
|
55
|
+
- **StalwartAdmin** — Admin API client for the Stalwart mail server. Creates and manages user accounts (called "principals"), manages domains, generates DKIM signing keys, sets the server hostname (important for email deliverability), configures outbound relay through Gmail SMTP (for domain mode when your server's IP doesn't have a PTR record), and performs health checks. Can restart the Stalwart Docker container when configuration changes require it.
|
|
56
|
+
|
|
57
|
+
### Security
|
|
58
|
+
- **scanOutboundEmail** — Scans every outgoing email before it's sent, looking for sensitive data that an AI agent shouldn't be leaking. Detects API keys (AWS, OpenAI, GitHub, Stripe, and many more), passwords, private keys (SSH, PGP, RSA), personally identifiable information (Social Security numbers, credit card numbers, bank account numbers, passport numbers, dates of birth, driver's licenses), database connection strings, JWT tokens, cryptocurrency wallet addresses, webhook URLs, environment variable blocks, and more. Also checks attachment filenames for risky file types. If any high-severity match is found, the email is blocked. Emails between local agents (`@localhost` recipients) skip scanning entirely.
|
|
59
|
+
- **sanitizeEmail** — Cleans up incoming email HTML to remove hidden content that could be used for prompt injection or phishing. Strips invisible Unicode characters (tag characters, zero-width joiners, bidirectional controls, soft hyphens), removes hidden HTML elements (display:none, visibility:hidden, font-size:0, white-on-white text, off-screen positioned elements, hidden iframes), removes script tags, strips data: and javascript: URIs, and removes suspicious HTML comments that contain words like "ignore", "system", "instruction", or "prompt". Returns both the cleaned content and a list of everything it found and removed.
|
|
60
|
+
- **scoreEmail** — Scores incoming email for spam and threat indicators using 47 pattern-matching rules across 9 categories. Returns a numeric score (0-100), whether it's classified as spam (score 40+) or a warning (score 20-39), the top threat category, and a list of every rule that matched with its score contribution.
|
|
61
|
+
- **isInternalEmail** — Detects whether an email is from another local agent (agent-to-agent communication on `@localhost`). Importantly, it recognizes relay emails — if the "from" address is `@localhost` but the reply-to address is external, it's a forwarded relay email and should be treated as external, not internal.
|
|
62
|
+
- **buildInboundSecurityAdvisory** — Analyzes incoming email attachments and spam matches to build a structured security advisory with risk levels (critical, high, medium) for attachments, double-extension detection (like `invoice.pdf.exe`), and link warnings.
|
|
63
|
+
|
|
64
|
+
### Storage
|
|
65
|
+
- **getDatabase** — Opens (or returns the existing) SQLite database with WAL mode enabled for better concurrent access. Automatically runs all pending migrations on first access. The database stores agent accounts, gateway configuration, pending blocked emails, delivered message tracking (for deduplication), spam logs, contacts, drafts, signatures, templates, scheduled emails, tags, email rules, agent tasks, and deletion reports.
|
|
66
|
+
- **EmailSearchIndex** — Full-text email search using SQLite FTS5. Indexes emails by agent, subject, sender, recipient, and body text. Search queries are run as phrase searches with FTS5 ranking for relevance ordering.
|
|
67
|
+
|
|
68
|
+
### Setup
|
|
69
|
+
- **SetupManager** — Handles first-time setup: checks if Docker, Stalwart, and cloudflared are installed, generates configuration files (docker-compose.yml, stalwart.toml, config.json, .env), creates the data directory, and initializes the database.
|
|
70
|
+
- **DependencyChecker** — Checks whether Docker, Stalwart (the Docker container), and cloudflared are installed and running. Returns version information for each.
|
|
71
|
+
- **DependencyInstaller** — Auto-installs missing dependencies. Installs Docker via Homebrew on macOS or the official script on Linux, starts the Stalwart container via docker-compose, and downloads the cloudflared binary from GitHub releases.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## How Agents Work
|
|
76
|
+
|
|
77
|
+
Every AI agent in AgenticMail is a real email user account on the Stalwart mail server. When you create an agent, the system:
|
|
78
|
+
|
|
79
|
+
1. Validates the agent name (must be letters, numbers, dots, hyphens, or underscores)
|
|
80
|
+
2. Generates a unique API key (starts with `ak_`, 48 random hex characters)
|
|
81
|
+
3. Generates a random password for SMTP/IMAP authentication
|
|
82
|
+
4. Creates a Stalwart mail server account (called a "principal") with the agent's email address
|
|
83
|
+
5. Stores everything in the SQLite database
|
|
84
|
+
|
|
85
|
+
Each agent has:
|
|
86
|
+
- A **name** (like "secretary" or "researcher") that's unique across the system
|
|
87
|
+
- An **email address** (like `secretary@localhost`, or `secretary@yourdomain.com` in domain mode)
|
|
88
|
+
- An **API key** for authenticating with the REST API
|
|
89
|
+
- A **role** (secretary, assistant, researcher, writer, or custom)
|
|
90
|
+
- **Metadata** — a flexible JSON field for storing anything (owner name, department, custom settings). Internal fields starting with `_` (like `_password` and `_gateway`) are protected and can't be overwritten by users.
|
|
91
|
+
|
|
92
|
+
Available roles: `secretary`, `assistant`, `researcher`, `writer`, `custom`
|
|
93
|
+
|
|
94
|
+
Agent names are lowercased when creating the Stalwart principal to match Stalwart's behavior.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Sending and Receiving Email
|
|
99
|
+
|
|
100
|
+
### Sending
|
|
101
|
+
|
|
102
|
+
The MailSender connects to Stalwart via SMTP (port 587 by default) and can send emails with:
|
|
103
|
+
- Plain text and/or HTML bodies
|
|
104
|
+
- File attachments (with filename, content buffer, and MIME type)
|
|
105
|
+
- CC and BCC recipients (single address or array)
|
|
106
|
+
- Reply-To address
|
|
107
|
+
- Threading headers (In-Reply-To and References) for proper email thread display
|
|
108
|
+
- Custom display name in the From header
|
|
109
|
+
- Custom SMTP headers
|
|
110
|
+
|
|
111
|
+
Connection timeouts are set to 10 seconds for initial connection and greeting, and 15 seconds for socket operations. TLS certificate verification is disabled for local development (the local Stalwart server uses self-signed certificates).
|
|
112
|
+
|
|
113
|
+
### Receiving
|
|
114
|
+
|
|
115
|
+
The MailReceiver connects to Stalwart via IMAP (port 143 by default) and provides:
|
|
116
|
+
- **List messages** — Returns email envelopes (UID, sender, recipients, subject, date, flags, size) with pagination. Default: 20 messages, max: 1000. Returns newest first.
|
|
117
|
+
- **Fetch full message** — Downloads the complete RFC822 email content for a specific message by UID.
|
|
118
|
+
- **Search** — Filter by sender, recipient, subject, body text, date range (since/before), and read/unread status.
|
|
119
|
+
- **Move messages** — Between folders (e.g., INBOX to Archive).
|
|
120
|
+
- **Mark read/unread** — Set or remove the IMAP \Seen flag.
|
|
121
|
+
- **Delete messages** — Mark for deletion.
|
|
122
|
+
- **Create folders** — Create new IMAP mailbox folders.
|
|
123
|
+
- **List folders** — Get all folders with their special-use attributes (Sent, Drafts, Trash, etc.) and flags.
|
|
124
|
+
- **Batch operations** — Mark multiple messages as read, unread, or deleted, or move multiple messages at once.
|
|
125
|
+
- **Fetch multiple** — Download several messages at once by UID list.
|
|
126
|
+
- **Append message** — Add a raw RFC822 message to a folder (used for copying sent emails to the Sent folder).
|
|
127
|
+
|
|
128
|
+
The receiver tracks its connection state and provides a `usable` property that checks both the connection flag and the underlying ImapFlow client's internal state, preventing operations on stale connections.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Real-Time Inbox Watching
|
|
133
|
+
|
|
134
|
+
The InboxWatcher uses IMAP IDLE to receive push notifications from the mail server when the inbox changes. This is much more efficient than polling — the server tells the watcher immediately when something happens.
|
|
135
|
+
|
|
136
|
+
Events emitted:
|
|
137
|
+
- **new** — A new email arrived. If `autoFetch` is enabled (default), the event includes the fully parsed email content. Otherwise, it just includes the UID.
|
|
138
|
+
- **expunge** — An email was deleted. Includes the IMAP sequence number.
|
|
139
|
+
- **flags** — An email's flags changed (e.g., marked as read). Includes the UID and new flag set.
|
|
140
|
+
- **error** — Something went wrong with the IMAP connection.
|
|
141
|
+
- **close** — The IMAP connection was closed.
|
|
142
|
+
|
|
143
|
+
The watcher holds a mailbox lock for the entire time it's running, which is necessary for IMAP IDLE to work. It creates a new IMAP connection each time `start()` is called because ImapFlow clients can't be reused after logout.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Internet Email (Gateway)
|
|
148
|
+
|
|
149
|
+
By default, agents can only email each other within the local Stalwart server. The gateway system adds the ability to send and receive real internet email through two modes:
|
|
150
|
+
|
|
151
|
+
### Relay Mode
|
|
152
|
+
|
|
153
|
+
Uses your existing Gmail or Outlook account as a middleman. This is the easiest way to get started — you don't need to buy a domain or configure DNS.
|
|
154
|
+
|
|
155
|
+
**How outbound works:** When an agent sends to an external address, the email goes through Gmail/Outlook SMTP. The "from" address uses sub-addressing: `you+agentname@gmail.com`. This way, replies automatically route back to the right agent.
|
|
156
|
+
|
|
157
|
+
**How inbound works:** The RelayGateway polls your Gmail/Outlook IMAP account every 30 seconds, looking for new messages. When it finds one addressed to `you+agentname@gmail.com`, it delivers it to that agent's local Stalwart mailbox. The first poll scans the 50 most recent messages; subsequent polls only check for new arrivals.
|
|
158
|
+
|
|
159
|
+
**Reliability:** If polling fails (network issue, auth problem, etc.), the system uses exponential backoff: 30 seconds → 1 minute → 2 minutes → 4 minutes, capping at 5 minutes between attempts. It never permanently stops — polling always reschedules. Every 5 consecutive failures, it logs detailed connection information for debugging. Each poll has a 30-second connection timeout to prevent hung connections.
|
|
160
|
+
|
|
161
|
+
**Agent routing:** When an inbound email arrives, the system figures out which agent it belongs to by checking (in order): sub-address in the To/CC/Delivered-To headers, In-Reply-To header matched against tracked sent messages, References header chain, or falls back to the default agent.
|
|
162
|
+
|
|
163
|
+
**Deduplication:** The system tracks delivered messages by (message_id, agent_name) to prevent the same email from being delivered twice. Sent message tracking keeps up to 10,000 entries in memory for reply routing.
|
|
164
|
+
|
|
165
|
+
### Domain Mode
|
|
166
|
+
|
|
167
|
+
Full custom domain through Cloudflare. Agents send from `agent@yourdomain.com` with proper email authentication (DKIM, SPF, DMARC).
|
|
168
|
+
|
|
169
|
+
**What gets set up automatically:**
|
|
170
|
+
1. Cloudflare DNS zone for the domain
|
|
171
|
+
2. Existing DNS records backed up to `~/.agenticmail/dns-backup-{domain}-{timestamp}.json`
|
|
172
|
+
3. Cloudflare Tunnel created (or reused if one with the same name exists)
|
|
173
|
+
4. Stalwart hostname set to the domain (critical for SMTP EHLO greeting)
|
|
174
|
+
5. DKIM signing key generated in Stalwart (selector: `agenticmail`)
|
|
175
|
+
6. DNS records configured: MX (via Email Routing), SPF, DMARC, DKIM TXT, tunnel CNAME
|
|
176
|
+
7. Tunnel started with ingress rules routing traffic to the API server (port 3100) and Stalwart (port 8080)
|
|
177
|
+
8. Cloudflare Email Routing enabled on the zone
|
|
178
|
+
9. Email Worker deployed — catches all inbound email, base64-encodes the raw RFC822 content, and POSTs it to the AgenticMail inbound webhook with a shared secret
|
|
179
|
+
10. Catch-all Email Routing rule set to route all `*@domain` to the Worker
|
|
180
|
+
11. Domain principal created in Stalwart
|
|
181
|
+
12. `@domain` email aliases added to all existing agent accounts
|
|
182
|
+
13. Configuration saved to the database
|
|
183
|
+
14. Optional: Gmail SMTP configured as outbound relay in Stalwart (for servers without PTR records)
|
|
184
|
+
|
|
185
|
+
**Outbound email flow:** The GatewayManager rewrites `@localhost` addresses to `@yourdomain.com`, then submits to local Stalwart via SMTP. Stalwart signs with DKIM and delivers directly to the recipient's mail server (or through the optional Gmail relay).
|
|
186
|
+
|
|
187
|
+
**Inbound email flow:** External email → Cloudflare Email Routing → catch-all rule → Email Worker → base64 encode → POST to `/api/agenticmail/mail/inbound` → parse, spam filter, deliver to agent's mailbox → InboxWatcher fires SSE event.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Keeping Things Safe
|
|
192
|
+
|
|
193
|
+
### Outbound Guard
|
|
194
|
+
|
|
195
|
+
Every outgoing email is scanned before sending. The guard looks for 34+ types of sensitive data patterns across 5 categories:
|
|
196
|
+
|
|
197
|
+
**Personally Identifiable Information (PII)**
|
|
198
|
+
- Social Security numbers (with dashes, dots, spaces, or in "SSN: 123456789" format)
|
|
199
|
+
- Credit card numbers (with or without separators)
|
|
200
|
+
- Bank routing and account numbers
|
|
201
|
+
- Driver's license numbers
|
|
202
|
+
- Dates of birth (multiple formats: MM/DD/YYYY, "born on Jan 15, 1990", etc.)
|
|
203
|
+
- Passport numbers
|
|
204
|
+
- Tax IDs and EINs
|
|
205
|
+
- ITINs (Individual Taxpayer Identification Numbers)
|
|
206
|
+
- Medicare/Medicaid/health insurance IDs
|
|
207
|
+
- Immigration A-numbers
|
|
208
|
+
- PIN codes
|
|
209
|
+
- Security question answers and mother's maiden name
|
|
210
|
+
|
|
211
|
+
**Credentials**
|
|
212
|
+
- API keys (generic `sk_`, `pk_`, `api_key_` patterns, plus OpenAI `sk-proj-` format)
|
|
213
|
+
- AWS access keys (AKIA prefix + 16 characters)
|
|
214
|
+
- Passwords (including leet-speak variants like `p4ssw0rd`)
|
|
215
|
+
- Private keys (RSA, EC, DSA, OpenSSH PEM blocks)
|
|
216
|
+
- Bearer tokens
|
|
217
|
+
- Database connection strings (MongoDB, PostgreSQL, MySQL, Redis, AMQP URIs)
|
|
218
|
+
- GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_, github_pat_ prefixes)
|
|
219
|
+
- Stripe API keys (sk_live_, pk_live_, etc.)
|
|
220
|
+
- JWT tokens (eyJ... pattern)
|
|
221
|
+
- Webhook URLs (Slack, Discord, webhook.site)
|
|
222
|
+
- Environment variable blocks (3+ consecutive KEY=value lines)
|
|
223
|
+
- Cryptocurrency seed/recovery phrases
|
|
224
|
+
- 2FA backup codes
|
|
225
|
+
- Username + password pairs
|
|
226
|
+
- OAuth access/refresh tokens
|
|
227
|
+
- VPN credentials
|
|
228
|
+
|
|
229
|
+
**Financial Information**
|
|
230
|
+
- IBAN numbers
|
|
231
|
+
- SWIFT/BIC codes
|
|
232
|
+
- Cryptocurrency wallet addresses (Bitcoin, Ethereum)
|
|
233
|
+
- Wire transfer instructions (with routing/account details)
|
|
234
|
+
|
|
235
|
+
**System Internals**
|
|
236
|
+
- Private IP addresses (10.x.x.x, 192.168.x.x, 172.16-31.x.x)
|
|
237
|
+
- Local file paths (/Users/, /home/, /etc/, C:\Users\, etc.)
|
|
238
|
+
- Environment variable assignments with sensitive suffixes (_URL, _KEY, _SECRET, _TOKEN, _PASSWORD, _HOST, _PORT, _DSN)
|
|
239
|
+
|
|
240
|
+
**Owner Privacy**
|
|
241
|
+
- Revealing the owner's personal info ("my owner's name/address/phone")
|
|
242
|
+
- Agent revealing operator details ("the person who runs me lives...")
|
|
243
|
+
|
|
244
|
+
**Attachment Scanning**
|
|
245
|
+
- High-risk extensions immediately flagged: .pem, .key, .p12, .pfx, .env, .credentials, .keystore, .jks, .p8
|
|
246
|
+
- Medium-risk extensions: .db, .sqlite, .sql, .csv, .json, .yml, .yaml, .conf, .config, .ini
|
|
247
|
+
- Text-scannable attachments (.txt, .csv, .json, .xml, .yaml, .md, .log, .env, .conf, .sql, .js, .ts, .py, .sh, .html, etc.) have their content scanned through all the same text rules
|
|
248
|
+
|
|
249
|
+
**How blocking works:** If any HIGH-severity match is found, the email is blocked entirely. The scan result includes the blocked flag, all warnings with their severity and category, and a match preview (up to 80 characters of the detected text). Medium-severity warnings don't block but are reported.
|
|
250
|
+
|
|
251
|
+
**Internal emails skip scanning:** If every recipient is `@localhost` (agent-to-agent communication), the entire scan is skipped — agents need to communicate freely with each other.
|
|
252
|
+
|
|
253
|
+
**HTML evasion prevention:** The guard strips all HTML tags and decodes HTML entities (including numeric entities like `A`) before scanning, so tricks like inserting `<b>` tags in the middle of an API key (`AKI<b>A</b>IOSFODNN7EXAMPLE`) don't work.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Email Approval Workflow
|
|
258
|
+
|
|
259
|
+
When the outbound guard blocks an email, here's what happens:
|
|
260
|
+
|
|
261
|
+
1. **Email is held** — The blocked email, along with all its warnings and a summary, is saved to the `pending_outbound` database table with a status of "pending".
|
|
262
|
+
|
|
263
|
+
2. **Owner is notified** — If a relay gateway is configured, the owner receives a notification email at their relay address (e.g., their Gmail). This notification includes:
|
|
264
|
+
- The agent's name and the subject line
|
|
265
|
+
- All security warnings with severity, rule ID, and description
|
|
266
|
+
- The full email content for review (complete headers: From, To, CC, BCC, Subject, Attachments, plus the full body text)
|
|
267
|
+
- The pending ID for reference
|
|
268
|
+
- Instructions: "Reply 'approve' to send it, or 'reject' to discard it"
|
|
269
|
+
|
|
270
|
+
3. **Owner can approve by replying** — The system stores the notification email's Message-ID. When the relay gateway polls for new mail and finds a reply to that notification, it extracts the first meaningful line of the reply (ignoring quoted text starting with `>`) and matches it against approval patterns:
|
|
271
|
+
- **Approve:** "approve", "approved", "yes", "send", "go ahead", "lgtm", "ok"
|
|
272
|
+
- **Reject:** "reject", "rejected", "no", "deny", "don't send", "cancel", "block"
|
|
273
|
+
|
|
274
|
+
If the owner replies "yes" or "approve", the system retrieves the blocked email from the database, sends it through the normal outbound path, marks the pending record as "approved" with `resolved_by: 'owner-reply'`, and sends the owner a confirmation email. If rejected, the record is marked as "rejected" and the owner gets a confirmation.
|
|
275
|
+
|
|
276
|
+
4. **Owner can approve via API** — The owner can also use the master key to call `POST /mail/pending/:id/approve` or `POST /mail/pending/:id/reject` through the API. Only the master key works — agent API keys are rejected with a 403 error.
|
|
277
|
+
|
|
278
|
+
5. **Agents cannot self-approve** — This is enforced at multiple levels:
|
|
279
|
+
- The API endpoints require the master key
|
|
280
|
+
- The MCP server rejects approve/reject actions with a message directing the agent to inform their owner
|
|
281
|
+
- The OpenClaw plugin does the same
|
|
282
|
+
- The OpenClaw system prompt explicitly instructs agents to never try to approve their own emails or rewrite them to avoid detection
|
|
283
|
+
|
|
284
|
+
6. **Agents can check status** — Agents can list and view their own pending emails to see if the owner has approved or rejected them. When using the master key, all pending emails across all agents are visible.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Spam Protection
|
|
289
|
+
|
|
290
|
+
Incoming emails are scored against 47 pattern-matching rules across 9 threat categories. Each rule has a point value, and points are added up to get a total score.
|
|
291
|
+
|
|
292
|
+
**Scoring thresholds:**
|
|
293
|
+
- Score 0-19: Clean, no action taken
|
|
294
|
+
- Score 20-39: Warning flag added, email delivered normally
|
|
295
|
+
- Score 40+: Classified as spam, moved to Spam folder
|
|
296
|
+
|
|
297
|
+
**Internal emails are exempt:** Emails between agents (`@localhost`) skip spam filtering entirely. The system also detects relay-rewritten emails — if the "from" shows `@localhost` but the reply-to is external, it's treated as an external email and gets scored.
|
|
298
|
+
|
|
299
|
+
### Threat Categories and Rules
|
|
300
|
+
|
|
301
|
+
**Prompt Injection (10 rules, 10-25 points each)**
|
|
302
|
+
Detects attempts to manipulate AI agents through email content:
|
|
303
|
+
- "Ignore previous instructions" / "ignore prior rules" (25 pts)
|
|
304
|
+
- "You are now a..." roleplay injection (25 pts)
|
|
305
|
+
- LLM delimiters: [SYSTEM], [INST], <<SYS>>, <|im_start|> (20 pts)
|
|
306
|
+
- "New instructions:" / "override instructions:" (20 pts)
|
|
307
|
+
- "Act as" / "pretend to be" (15 pts)
|
|
308
|
+
- "Do not mention/tell/reveal" suppression (15 pts)
|
|
309
|
+
- Invisible Unicode: tag characters (U+E0001-E007F), dense zero-width characters (20 pts)
|
|
310
|
+
- "DAN", "jailbreak", "bypass safety/filter" (20 pts)
|
|
311
|
+
- Long base64 blocks (100+ characters) that could hide instructions (15 pts)
|
|
312
|
+
- Code block injection: ```system, ```python exec (10 pts)
|
|
313
|
+
|
|
314
|
+
**Social Engineering (7 rules, 10-20 points each)**
|
|
315
|
+
- "Your owner/admin asked/told/wants you to..." impersonation (20 pts)
|
|
316
|
+
- "Share your API key/password/secret" (15 pts)
|
|
317
|
+
- "This is a system/security automated message" (15 pts)
|
|
318
|
+
- Urgency language combined with authority/threat language (10 pts)
|
|
319
|
+
- "Send me $X", "wire transfer", "Western Union" (15 pts)
|
|
320
|
+
- "Buy me gift cards", iTunes/Google Play cards (20 pts)
|
|
321
|
+
- CEO/CFO fraud: executive title + payment/wire/urgent (15 pts)
|
|
322
|
+
|
|
323
|
+
**Data Exfiltration (5 rules, 15-20 points each)**
|
|
324
|
+
- "Forward all/every emails" (20 pts)
|
|
325
|
+
- "Search inbox for password" / "find credentials" (20 pts)
|
|
326
|
+
- "Send the/all/every ... to external@email.com" (15 pts)
|
|
327
|
+
- "Reveal system prompt" / "dump instructions" (15 pts)
|
|
328
|
+
- Webhook/ngrok/pipedream/requestbin URLs (15 pts)
|
|
329
|
+
|
|
330
|
+
**Phishing (8 rules, 3-15 points each)**
|
|
331
|
+
- Sender name contains brand (Google, Microsoft, Apple, Amazon, PayPal, Meta, Netflix, Bank) but domain doesn't match (10 pts)
|
|
332
|
+
- "Verify your account/password" with links present (15 pts)
|
|
333
|
+
- Links with IP addresses, URL shorteners (bit.ly, t.co, etc.), or excessive subdomains (10 pts)
|
|
334
|
+
- data:text/html or javascript: URIs in links (15 pts)
|
|
335
|
+
- Punycode domains (xn--) or mixed Cyrillic+Latin scripts in sender domain (15 pts)
|
|
336
|
+
- Link text shows one URL but href points to a different domain (10 pts)
|
|
337
|
+
- "Click here/sign in" combined with urgency (expire/suspend/locked) (10 pts)
|
|
338
|
+
- 5+ unique links but no List-Unsubscribe header (3 pts)
|
|
339
|
+
|
|
340
|
+
**Authentication (4 rules, 3-20 points each)**
|
|
341
|
+
- SPF authentication failed (15 pts)
|
|
342
|
+
- DKIM authentication failed (15 pts)
|
|
343
|
+
- DMARC authentication failed (20 pts)
|
|
344
|
+
- No Authentication-Results header at all (3 pts)
|
|
345
|
+
|
|
346
|
+
**Attachment Risk (4 rules, 10-25 points each)**
|
|
347
|
+
- Executable files: .exe, .bat, .cmd, .ps1, .sh, .dll, .scr, .vbs, .js, .msi, .com (25 pts)
|
|
348
|
+
- Double extensions: document.pdf.exe (20 pts)
|
|
349
|
+
- Archive files: .zip, .rar, .7z, .tar.gz (15 pts)
|
|
350
|
+
- HTML/SVG attachments (10 pts)
|
|
351
|
+
|
|
352
|
+
**Header Anomalies (3 rules, 5-10 points each)**
|
|
353
|
+
- Missing Message-ID header (5 pts)
|
|
354
|
+
- Empty or missing From address (10 pts)
|
|
355
|
+
- Reply-To domain differs from From domain (5 pts)
|
|
356
|
+
|
|
357
|
+
**Content Spam (7 rules, 3-25 points each)**
|
|
358
|
+
- Subject >80% uppercase (min 10 chars) (5 pts)
|
|
359
|
+
- Lottery/prize language, "Nigerian prince" (25 pts)
|
|
360
|
+
- Crypto investment scams, "guaranteed returns" (10 pts)
|
|
361
|
+
- 4+ exclamation marks or question marks in subject (3 pts)
|
|
362
|
+
- Pharmacy spam: Viagra, Cialis, "online pharmacy" (15 pts)
|
|
363
|
+
- Weight loss scams: "diet pill", "lose 30 lbs" (10 pts)
|
|
364
|
+
- HTML-only email with no plain text (5 pts)
|
|
365
|
+
- High density of spam words (congratulations, winner, prize, claim, free, limited time, act now, click here, etc.) — 10 pts if >5 words, 20 pts if >10 words
|
|
366
|
+
|
|
367
|
+
**Link Analysis (1 rule, 5 points)**
|
|
368
|
+
- 10+ unique links in one email (5 pts)
|
|
369
|
+
|
|
370
|
+
Each rule is wrapped in error handling so a failure in one rule doesn't crash the entire spam filter.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Email Sanitization
|
|
375
|
+
|
|
376
|
+
Before showing email content to an AI agent, the sanitizer cleans it to remove hidden content that could manipulate the agent or trick it into taking harmful actions.
|
|
377
|
+
|
|
378
|
+
**Invisible Unicode removal:**
|
|
379
|
+
- Tag characters (U+E0001-E007F) — invisible characters that could encode hidden messages
|
|
380
|
+
- Zero-width characters (U+200B, U+200C, U+200D, U+FEFF) — invisible when 3+ appear together
|
|
381
|
+
- Bidirectional control characters (U+202A-202E, U+2066-2069) — can make text display in a different order than it actually is
|
|
382
|
+
- Soft hyphens (U+00AD) — invisible in most contexts
|
|
383
|
+
- Word joiners (U+2060) — invisible spacing control
|
|
384
|
+
|
|
385
|
+
**Hidden HTML removal:**
|
|
386
|
+
- Elements styled with `display:none`, `visibility:hidden`, `font-size:0`, or `opacity:0`
|
|
387
|
+
- White-on-white text (same foreground and background color)
|
|
388
|
+
- Elements positioned off-screen with extreme negative coordinates (left:-9999px, etc.)
|
|
389
|
+
- Script tags and their contents
|
|
390
|
+
- `data:text/html` and `javascript:` URIs in src, href, or action attributes
|
|
391
|
+
- HTML comments containing suspicious words: "ignore", "system", "instruction", "prompt", "inject"
|
|
392
|
+
- Hidden iframes (width=0, height=0, or display:none)
|
|
393
|
+
|
|
394
|
+
The sanitizer returns both the cleaned content and a detailed list of every detection it made (type, description, count), along with a flag indicating whether any changes were made.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Search
|
|
399
|
+
|
|
400
|
+
The EmailSearchIndex provides full-text search across all indexed emails using SQLite FTS5.
|
|
401
|
+
|
|
402
|
+
Emails are indexed by: agent ID, message ID, subject, sender address, recipient address, body text, and received date.
|
|
403
|
+
|
|
404
|
+
Search queries are automatically wrapped in quotes for phrase matching and sanitized to prevent FTS5 injection. Results are ranked by relevance using FTS5's built-in ranking algorithm. If a search query has invalid syntax, it returns an empty result set rather than throwing an error.
|
|
405
|
+
|
|
406
|
+
Limits: minimum 1 result, maximum 1000 results per query (default 20).
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Data Storage
|
|
411
|
+
|
|
412
|
+
AgenticMail uses a SQLite database with WAL mode (Write-Ahead Logging) for better concurrent access and foreign keys enabled for referential integrity. The database is initialized with automatic migrations that run on first access.
|
|
413
|
+
|
|
414
|
+
**Database location:** `~/.agenticmail/agenticmail.db`
|
|
415
|
+
|
|
416
|
+
**Tables (13 migrations):**
|
|
417
|
+
- **agents** — Agent accounts with name, email, API key, Stalwart principal, role, metadata (JSON), creation/update timestamps, last activity timestamp, and persistent flag
|
|
418
|
+
- **domains** — Custom email domains with Stalwart principal, DKIM selector and public key, verification status
|
|
419
|
+
- **config** — Key-value configuration store (e.g., `relay_last_seen_uid` for relay polling state)
|
|
420
|
+
- **email_search** — FTS5 virtual table for full-text email search
|
|
421
|
+
- **gateway_config** — Gateway mode and configuration (JSON), single row with id='default'
|
|
422
|
+
- **purchased_domains** — Purchased domain records with Cloudflare zone ID, tunnel ID, DNS/tunnel status
|
|
423
|
+
- **delivered_messages** — Relay deduplication tracking (message_id + agent_name composite key)
|
|
424
|
+
- **pending_outbound** — Blocked outbound emails awaiting approval, with mail options (JSON), warnings (JSON), status (pending/approved/rejected), notification message ID (for reply matching), resolution info
|
|
425
|
+
- **contacts** — Per-agent address book
|
|
426
|
+
- **drafts** — Per-agent draft emails
|
|
427
|
+
- **signatures** — Per-agent email signatures (one default per agent)
|
|
428
|
+
- **templates** — Per-agent reusable email templates
|
|
429
|
+
- **scheduled_emails** — Emails scheduled for future delivery (pending/sent/error status)
|
|
430
|
+
- **tags** — Per-agent message tags with colors
|
|
431
|
+
- **message_tags** — Many-to-many relationship between tags and messages (with folder context)
|
|
432
|
+
- **email_rules** — Per-agent email filtering rules with conditions (JSON) and actions (JSON), priority ordering
|
|
433
|
+
- **agent_deletions** — Audit trail for deleted agents with full archived report (JSON), file path to backup
|
|
434
|
+
- **agent_tasks** — Inter-agent task assignments with type, payload (JSON), status (pending/claimed/completed/failed), result, timestamps
|
|
435
|
+
- **spam_log** — Spam scoring history per agent per message
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Setup and Dependencies
|
|
440
|
+
|
|
441
|
+
The SetupManager handles getting everything installed and configured for the first time:
|
|
442
|
+
|
|
443
|
+
**Dependency checking:**
|
|
444
|
+
- Docker — checks `docker --version` for version
|
|
445
|
+
- Stalwart — checks `docker ps` for the `agenticmail-stalwart` container
|
|
446
|
+
- cloudflared — checks managed binary at `~/.agenticmail/bin/cloudflared` or system-wide via `which`
|
|
447
|
+
|
|
448
|
+
**Automatic installation:**
|
|
449
|
+
- Docker: via Homebrew (`brew install --cask docker`) on macOS, or the official install script on Linux. Opens Docker Desktop on macOS and waits up to 60 seconds for the daemon to start.
|
|
450
|
+
- Stalwart: starts the container via `docker compose up -d` and waits up to 30 seconds for it to be running.
|
|
451
|
+
- cloudflared: downloads the platform-specific binary from GitHub releases (supports macOS ARM/Intel and Linux ARM/Intel). Installs atomically (write to temp file, chmod, rename) at `~/.agenticmail/bin/cloudflared`.
|
|
452
|
+
|
|
453
|
+
**Configuration generation:**
|
|
454
|
+
- `docker-compose.yml` — Stalwart service with ports 8080 (HTTP admin), 587 (SMTP submission), 143 (IMAP), 25 (SMTP inbound)
|
|
455
|
+
- `stalwart.toml` — Stalwart configuration with RocksDB storage, internal directory, stdout logging, and fallback admin credentials
|
|
456
|
+
- `config.json` — Master key, Stalwart URL/credentials, SMTP/IMAP host/port, API host/port, data directory (written with mode 0600 for security)
|
|
457
|
+
- `.env` — Environment variables (written with mode 0600)
|
|
458
|
+
|
|
459
|
+
Configuration files are placed in the data directory (default: `~/.agenticmail/`). Calling `initConfig()` is idempotent — it loads existing config if present, but always regenerates Docker files to keep passwords in sync.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## License
|
|
464
|
+
|
|
465
|
+
[MIT](./LICENSE) - Ope Olatunji ([@ope-olatunji](https://github.com/ope-olatunji))
|