mails 0.0.8 → 1.0.0

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/dist/index.js ADDED
@@ -0,0 +1,411 @@
1
+ // @bun
2
+ // src/core/config.ts
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
+ import { homedir } from "os";
5
+ import { join } from "path";
6
+ var CONFIG_DIR = join(homedir(), ".mails");
7
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
8
+ var DEFAULT_CONFIG = {
9
+ mode: "hosted",
10
+ domain: "mails.dev",
11
+ mailbox: "",
12
+ send_provider: "resend",
13
+ storage_provider: "sqlite"
14
+ };
15
+ function ensureDir() {
16
+ mkdirSync(CONFIG_DIR, { recursive: true });
17
+ }
18
+ function loadConfig() {
19
+ ensureDir();
20
+ if (!existsSync(CONFIG_FILE)) {
21
+ return { ...DEFAULT_CONFIG };
22
+ }
23
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
24
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
25
+ }
26
+ function saveConfig(config) {
27
+ ensureDir();
28
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + `
29
+ `);
30
+ }
31
+ function getConfigValue(key) {
32
+ const config = { ...loadConfig() };
33
+ return config[key];
34
+ }
35
+ function setConfigValue(key, value) {
36
+ const config = { ...loadConfig() };
37
+ config[key] = value;
38
+ saveConfig(config);
39
+ }
40
+
41
+ // src/providers/send/resend.ts
42
+ function createResendProvider(apiKey) {
43
+ return {
44
+ name: "resend",
45
+ async send(options) {
46
+ const body = {
47
+ from: options.from,
48
+ to: options.to,
49
+ subject: options.subject
50
+ };
51
+ if (options.text)
52
+ body.text = options.text;
53
+ if (options.html)
54
+ body.html = options.html;
55
+ if (options.replyTo)
56
+ body.reply_to = options.replyTo;
57
+ const res = await fetch("https://api.resend.com/emails", {
58
+ method: "POST",
59
+ headers: {
60
+ Authorization: `Bearer ${apiKey}`,
61
+ "Content-Type": "application/json"
62
+ },
63
+ body: JSON.stringify(body)
64
+ });
65
+ if (!res.ok) {
66
+ const err = await res.json();
67
+ throw new Error(`Resend error: ${err.message}`);
68
+ }
69
+ const data = await res.json();
70
+ return { id: data.id, provider: "resend" };
71
+ }
72
+ };
73
+ }
74
+
75
+ // src/core/send.ts
76
+ function resolveProvider() {
77
+ const config = loadConfig();
78
+ switch (config.send_provider) {
79
+ case "resend": {
80
+ if (!config.resend_api_key) {
81
+ throw new Error("resend_api_key not configured. Run: mails config set resend_api_key <key>");
82
+ }
83
+ return createResendProvider(config.resend_api_key);
84
+ }
85
+ default:
86
+ throw new Error(`Unknown send provider: ${config.send_provider}`);
87
+ }
88
+ }
89
+ async function send(options) {
90
+ const config = loadConfig();
91
+ const provider = resolveProvider();
92
+ const from = options.from ?? config.default_from;
93
+ if (!from) {
94
+ throw new Error('No "from" address. Set default_from or pass --from');
95
+ }
96
+ const to = Array.isArray(options.to) ? options.to : [options.to];
97
+ if (!options.text && !options.html) {
98
+ throw new Error("Either text or html body is required");
99
+ }
100
+ return provider.send({
101
+ from,
102
+ to,
103
+ subject: options.subject,
104
+ text: options.text,
105
+ html: options.html,
106
+ replyTo: options.replyTo
107
+ });
108
+ }
109
+ // src/providers/storage/sqlite.ts
110
+ import { Database } from "bun:sqlite";
111
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
112
+ import { homedir as homedir2 } from "os";
113
+ import { join as join2 } from "path";
114
+ var SCHEMA = `
115
+ CREATE TABLE IF NOT EXISTS emails (
116
+ id TEXT PRIMARY KEY,
117
+ mailbox TEXT NOT NULL,
118
+ from_address TEXT NOT NULL,
119
+ from_name TEXT DEFAULT '',
120
+ to_address TEXT NOT NULL,
121
+ subject TEXT DEFAULT '',
122
+ body_text TEXT DEFAULT '',
123
+ body_html TEXT DEFAULT '',
124
+ code TEXT,
125
+ headers TEXT DEFAULT '{}',
126
+ metadata TEXT DEFAULT '{}',
127
+ direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')),
128
+ status TEXT DEFAULT 'received' CHECK (status IN ('received', 'sent', 'failed', 'queued')),
129
+ received_at TEXT NOT NULL,
130
+ created_at TEXT NOT NULL
131
+ );
132
+ CREATE INDEX IF NOT EXISTS idx_emails_mailbox ON emails(mailbox, received_at DESC);
133
+ CREATE INDEX IF NOT EXISTS idx_emails_code ON emails(mailbox) WHERE code IS NOT NULL;
134
+ `;
135
+ function createSqliteProvider(dbPath) {
136
+ const dir = join2(homedir2(), ".mails");
137
+ if (!existsSync2(dir))
138
+ mkdirSync2(dir, { recursive: true });
139
+ const path = dbPath ?? join2(dir, "mails.db");
140
+ let db;
141
+ return {
142
+ name: "sqlite",
143
+ async init() {
144
+ db = new Database(path);
145
+ db.exec("PRAGMA journal_mode=WAL;");
146
+ db.exec(SCHEMA);
147
+ },
148
+ async saveEmail(email) {
149
+ db.prepare(`
150
+ INSERT OR REPLACE INTO emails (id, mailbox, from_address, from_name, to_address, subject, body_text, body_html, code, headers, metadata, direction, status, received_at, created_at)
151
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
152
+ `).run(email.id, email.mailbox, email.from_address, email.from_name, email.to_address, email.subject, email.body_text, email.body_html, email.code, JSON.stringify(email.headers), JSON.stringify(email.metadata), email.direction, email.status, email.received_at, email.created_at);
153
+ },
154
+ async getEmails(mailbox, options) {
155
+ const limit = options?.limit ?? 20;
156
+ const offset = options?.offset ?? 0;
157
+ let query = "SELECT * FROM emails WHERE mailbox = ?";
158
+ const params = [mailbox];
159
+ if (options?.direction) {
160
+ query += " AND direction = ?";
161
+ params.push(options.direction);
162
+ }
163
+ query += " ORDER BY received_at DESC LIMIT ? OFFSET ?";
164
+ params.push(limit, offset);
165
+ const rows = db.prepare(query).all(...params);
166
+ return rows.map(rowToEmail);
167
+ },
168
+ async getEmail(id) {
169
+ const row = db.prepare("SELECT * FROM emails WHERE id = ?").get(id);
170
+ return row ? rowToEmail(row) : null;
171
+ },
172
+ async getCode(mailbox, options) {
173
+ const timeout = (options?.timeout ?? 30) * 1000;
174
+ const since = options?.since;
175
+ const deadline = Date.now() + timeout;
176
+ while (Date.now() < deadline) {
177
+ let query = "SELECT code, from_address, subject FROM emails WHERE mailbox = ? AND code IS NOT NULL";
178
+ const params = [mailbox];
179
+ if (since) {
180
+ query += " AND received_at > ?";
181
+ params.push(since);
182
+ }
183
+ query += " ORDER BY received_at DESC LIMIT 1";
184
+ const row = db.prepare(query).get(...params);
185
+ if (row) {
186
+ return { code: row.code, from: row.from_address, subject: row.subject };
187
+ }
188
+ await new Promise((r) => setTimeout(r, 1000));
189
+ }
190
+ return null;
191
+ }
192
+ };
193
+ }
194
+ function rowToEmail(row) {
195
+ return {
196
+ id: row.id,
197
+ mailbox: row.mailbox,
198
+ from_address: row.from_address,
199
+ from_name: row.from_name ?? "",
200
+ to_address: row.to_address,
201
+ subject: row.subject ?? "",
202
+ body_text: row.body_text ?? "",
203
+ body_html: row.body_html ?? "",
204
+ code: row.code ?? null,
205
+ headers: safeJsonParse(row.headers, {}),
206
+ metadata: safeJsonParse(row.metadata, {}),
207
+ direction: row.direction,
208
+ status: row.status,
209
+ received_at: row.received_at,
210
+ created_at: row.created_at
211
+ };
212
+ }
213
+ function safeJsonParse(str, fallback) {
214
+ if (!str)
215
+ return fallback;
216
+ try {
217
+ return JSON.parse(str);
218
+ } catch {
219
+ return fallback;
220
+ }
221
+ }
222
+
223
+ // src/providers/storage/db9.ts
224
+ var SCHEMA2 = `
225
+ CREATE TABLE IF NOT EXISTS emails (
226
+ id TEXT PRIMARY KEY,
227
+ mailbox TEXT NOT NULL,
228
+ from_address TEXT NOT NULL,
229
+ from_name TEXT DEFAULT '',
230
+ to_address TEXT NOT NULL,
231
+ subject TEXT DEFAULT '',
232
+ body_text TEXT DEFAULT '',
233
+ body_html TEXT DEFAULT '',
234
+ code TEXT,
235
+ headers JSONB DEFAULT '{}',
236
+ metadata JSONB DEFAULT '{}',
237
+ direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')),
238
+ status TEXT DEFAULT 'received' CHECK (status IN ('received', 'sent', 'failed', 'queued')),
239
+ received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
240
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
241
+ );
242
+ CREATE INDEX IF NOT EXISTS idx_emails_mailbox ON emails(mailbox, received_at DESC);
243
+ CREATE INDEX IF NOT EXISTS idx_emails_code ON emails(mailbox) WHERE code IS NOT NULL;
244
+ `;
245
+ function createDb9Provider(token, databaseId) {
246
+ const baseUrl = "https://api.db9.ai";
247
+ async function sql(query) {
248
+ const res = await fetch(`${baseUrl}/customer/databases/${databaseId}/sql`, {
249
+ method: "POST",
250
+ headers: {
251
+ Authorization: `Bearer ${token}`,
252
+ "Content-Type": "application/json"
253
+ },
254
+ body: JSON.stringify({ query })
255
+ });
256
+ if (!res.ok) {
257
+ const text = await res.text();
258
+ throw new Error(`db9 error (${res.status}): ${text}`);
259
+ }
260
+ return await res.json();
261
+ }
262
+ function rowsToEmails(result) {
263
+ return result.rows.map((row) => {
264
+ const obj = {};
265
+ result.columns.forEach((col, i) => {
266
+ obj[col] = row[i];
267
+ });
268
+ return {
269
+ id: obj.id,
270
+ mailbox: obj.mailbox,
271
+ from_address: obj.from_address,
272
+ from_name: obj.from_name ?? "",
273
+ to_address: obj.to_address,
274
+ subject: obj.subject ?? "",
275
+ body_text: obj.body_text ?? "",
276
+ body_html: obj.body_html ?? "",
277
+ code: obj.code ?? null,
278
+ headers: typeof obj.headers === "string" ? JSON.parse(obj.headers) : obj.headers ?? {},
279
+ metadata: typeof obj.metadata === "string" ? JSON.parse(obj.metadata) : obj.metadata ?? {},
280
+ direction: obj.direction,
281
+ status: obj.status,
282
+ received_at: obj.received_at,
283
+ created_at: obj.created_at
284
+ };
285
+ });
286
+ }
287
+ return {
288
+ name: "db9",
289
+ async init() {
290
+ await sql(SCHEMA2);
291
+ },
292
+ async saveEmail(email) {
293
+ const esc = (s) => s.replace(/'/g, "''");
294
+ await sql(`
295
+ INSERT INTO emails (id, mailbox, from_address, from_name, to_address, subject, body_text, body_html, code, headers, metadata, direction, status, received_at, created_at)
296
+ VALUES (
297
+ '${esc(email.id)}', '${esc(email.mailbox)}', '${esc(email.from_address)}', '${esc(email.from_name)}',
298
+ '${esc(email.to_address)}', '${esc(email.subject)}', '${esc(email.body_text)}', '${esc(email.body_html)}',
299
+ ${email.code ? `'${esc(email.code)}'` : "NULL"},
300
+ '${esc(JSON.stringify(email.headers))}'::jsonb,
301
+ '${esc(JSON.stringify(email.metadata))}'::jsonb,
302
+ '${esc(email.direction)}', '${esc(email.status)}',
303
+ '${esc(email.received_at)}', '${esc(email.created_at)}'
304
+ )
305
+ ON CONFLICT (id) DO UPDATE SET
306
+ status = EXCLUDED.status,
307
+ metadata = EXCLUDED.metadata
308
+ `);
309
+ },
310
+ async getEmails(mailbox, options) {
311
+ const limit = options?.limit ?? 20;
312
+ const offset = options?.offset ?? 0;
313
+ const esc = (s) => s.replace(/'/g, "''");
314
+ let query = `SELECT * FROM emails WHERE mailbox = '${esc(mailbox)}'`;
315
+ if (options?.direction) {
316
+ query += ` AND direction = '${esc(options.direction)}'`;
317
+ }
318
+ query += ` ORDER BY received_at DESC LIMIT ${limit} OFFSET ${offset}`;
319
+ const result = await sql(query);
320
+ return rowsToEmails(result);
321
+ },
322
+ async getEmail(id) {
323
+ const esc = (s) => s.replace(/'/g, "''");
324
+ const result = await sql(`SELECT * FROM emails WHERE id = '${esc(id)}' LIMIT 1`);
325
+ const emails = rowsToEmails(result);
326
+ return emails[0] ?? null;
327
+ },
328
+ async getCode(mailbox, options) {
329
+ const timeout = (options?.timeout ?? 30) * 1000;
330
+ const since = options?.since;
331
+ const esc = (s) => s.replace(/'/g, "''");
332
+ const deadline = Date.now() + timeout;
333
+ while (Date.now() < deadline) {
334
+ let query = `SELECT code, from_address, subject FROM emails WHERE mailbox = '${esc(mailbox)}' AND code IS NOT NULL`;
335
+ if (since) {
336
+ query += ` AND received_at > '${esc(since)}'`;
337
+ }
338
+ query += " ORDER BY received_at DESC LIMIT 1";
339
+ const result = await sql(query);
340
+ if (result.row_count > 0) {
341
+ const row = result.rows[0];
342
+ const codeIdx = result.columns.indexOf("code");
343
+ const fromIdx = result.columns.indexOf("from_address");
344
+ const subIdx = result.columns.indexOf("subject");
345
+ return {
346
+ code: row[codeIdx],
347
+ from: row[fromIdx],
348
+ subject: row[subIdx]
349
+ };
350
+ }
351
+ await new Promise((r) => setTimeout(r, 2000));
352
+ }
353
+ return null;
354
+ }
355
+ };
356
+ }
357
+
358
+ // src/core/storage.ts
359
+ var _provider = null;
360
+ async function getStorage() {
361
+ if (_provider)
362
+ return _provider;
363
+ const config = loadConfig();
364
+ switch (config.storage_provider) {
365
+ case "db9": {
366
+ if (!config.db9_token) {
367
+ throw new Error("db9_token not configured. Run: mails config set db9_token <token>");
368
+ }
369
+ if (!config.db9_database_id) {
370
+ throw new Error("db9_database_id not configured. Run: mails config set db9_database_id <id>");
371
+ }
372
+ _provider = createDb9Provider(config.db9_token, config.db9_database_id);
373
+ break;
374
+ }
375
+ case "sqlite":
376
+ default: {
377
+ _provider = createSqliteProvider();
378
+ break;
379
+ }
380
+ }
381
+ await _provider.init();
382
+ return _provider;
383
+ }
384
+
385
+ // src/core/receive.ts
386
+ async function getInbox(mailbox, options) {
387
+ const storage = await getStorage();
388
+ return storage.getEmails(mailbox, options);
389
+ }
390
+ async function getEmail(id) {
391
+ const storage = await getStorage();
392
+ return storage.getEmail(id);
393
+ }
394
+ async function waitForCode(mailbox, options) {
395
+ const storage = await getStorage();
396
+ return storage.getCode(mailbox, options);
397
+ }
398
+ export {
399
+ waitForCode,
400
+ setConfigValue,
401
+ send,
402
+ saveConfig,
403
+ loadConfig,
404
+ getStorage,
405
+ getInbox,
406
+ getEmail,
407
+ getConfigValue,
408
+ createSqliteProvider,
409
+ createResendProvider,
410
+ createDb9Provider
411
+ };
package/package.json CHANGED
@@ -1,34 +1,47 @@
1
1
  {
2
2
  "name": "mails",
3
- "version": "0.0.8",
4
- "description": "send beautiful emails made easy, with sexy templates built in.",
5
- "main": "index.js",
6
- "author": "turing",
7
- "license": "MIT",
8
- "bin": "./bin/cli",
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
3
+ "version": "1.0.0",
4
+ "description": "Email infrastructure for AI agents",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "mails": "dist/cli.js"
11
10
  },
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/turingou/mails.git"
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "bun build src/cli/index.ts --outfile dist/cli.js --target bun && bun build src/index.ts --outfile dist/index.js --target bun",
19
+ "build:compile": "bun build src/cli/index.ts --compile --outfile mails",
20
+ "dev": "bun run src/cli/index.ts",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "bun test test/unit test/e2e/flow.test.ts",
23
+ "test:coverage": "bun test --coverage test/unit test/e2e/flow.test.ts",
24
+ "test:live": "bun test test/e2e/live.test.ts",
25
+ "test:all": "bun test"
15
26
  },
27
+ "files": [
28
+ "dist",
29
+ "skill.md"
30
+ ],
16
31
  "keywords": [
32
+ "email",
17
33
  "mail",
18
- "mails",
19
- "email"
34
+ "agent",
35
+ "ai",
36
+ "resend",
37
+ "cloudflare",
38
+ "cli"
20
39
  ],
21
- "bugs": {
22
- "url": "https://github.com/turingou/mails/issues"
23
- },
24
- "dependencies": {
25
- "tao": "0.0.2",
26
- "exeq": "~0.4.0",
27
- "juice": "~0.4.0",
28
- "optimist": "*",
29
- "consoler": "*",
30
- "mails-default": "*",
31
- "nodemailer": "~0.5.7",
32
- "pkghub-render": "*"
40
+ "author": "turing <o.u.turing@gmail.com>",
41
+ "license": "MIT",
42
+ "dependencies": {},
43
+ "devDependencies": {
44
+ "@types/bun": "^1.2.0",
45
+ "typescript": "^5.7.0"
33
46
  }
34
- }
47
+ }
package/skill.md ADDED
@@ -0,0 +1,213 @@
1
+ # mails — Email for AI Agents
2
+
3
+ Send and receive emails programmatically. Supports custom domains (self-hosted) or zero-config `@mails.dev` addresses (hosted).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install
9
+ npm install -g mails # or: bunx mails
10
+
11
+ # Configure
12
+ mails config set resend_api_key re_YOUR_KEY
13
+ mails config set default_from "Agent <agent@yourdomain.com>"
14
+
15
+ # Send
16
+ mails send --to user@example.com --subject "Hello" --body "World"
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ Config lives at `~/.mails/config.json`. Set values via CLI:
22
+
23
+ ```bash
24
+ mails config set <key> <value>
25
+ mails config get <key>
26
+ mails config # show all
27
+ ```
28
+
29
+ ### Required Keys
30
+
31
+ | Key | Description |
32
+ |-----|-------------|
33
+ | `resend_api_key` | Your Resend API key (get one at resend.com) |
34
+ | `default_from` | Default sender, e.g. `"Agent <agent@yourdomain.com>"` |
35
+
36
+ ### Optional Keys
37
+
38
+ | Key | Default | Description |
39
+ |-----|---------|-------------|
40
+ | `mode` | `hosted` | `hosted` (use @mails.dev) or `selfhosted` (custom domain) |
41
+ | `domain` | `mails.dev` | Your email domain |
42
+ | `mailbox` | | Your receiving address |
43
+ | `storage_provider` | `sqlite` | `sqlite` (local) or `db9` (db9.ai cloud) |
44
+ | `db9_token` | | db9.ai API token |
45
+ | `db9_database_id` | | db9.ai database ID |
46
+
47
+ ## Sending Emails
48
+
49
+ ### CLI
50
+
51
+ ```bash
52
+ # Plain text
53
+ mails send --to user@example.com --subject "Report" --body "Here is your report."
54
+
55
+ # HTML
56
+ mails send --to user@example.com --subject "Report" --html "<h1>Report</h1><p>Details...</p>"
57
+
58
+ # Custom sender
59
+ mails send --from "Bot <bot@mydomain.com>" --to user@example.com --subject "Hi" --body "Hello"
60
+ ```
61
+
62
+ ### Programmatic (SDK)
63
+
64
+ ```typescript
65
+ import { send } from 'mails'
66
+
67
+ const result = await send({
68
+ to: 'user@example.com',
69
+ subject: 'Hello from agent',
70
+ text: 'This is a test email.',
71
+ })
72
+ console.log(result.id) // Resend message ID
73
+ ```
74
+
75
+ ### Programmatic (Direct Provider)
76
+
77
+ ```typescript
78
+ import { createResendProvider } from 'mails'
79
+
80
+ const provider = createResendProvider('re_YOUR_KEY')
81
+ const result = await provider.send({
82
+ from: 'Agent <agent@yourdomain.com>',
83
+ to: ['user@example.com'],
84
+ subject: 'Hello',
85
+ text: 'Direct provider usage.',
86
+ })
87
+ ```
88
+
89
+ ## Receiving Emails
90
+
91
+ Requires a Cloudflare Email Routing Worker or the mails.dev hosted service. Once configured:
92
+
93
+ ```bash
94
+ # List inbox
95
+ mails inbox
96
+ mails inbox --mailbox agent@yourdomain.com
97
+
98
+ # Wait for verification code (long-poll)
99
+ mails code --to agent@yourdomain.com --timeout 30
100
+ ```
101
+
102
+ ### SDK
103
+
104
+ ```typescript
105
+ import { getInbox, waitForCode } from 'mails'
106
+
107
+ // List recent emails
108
+ const emails = await getInbox('agent@yourdomain.com', { limit: 10 })
109
+
110
+ // Wait for a verification code
111
+ const result = await waitForCode('agent@yourdomain.com', { timeout: 30 })
112
+ if (result) {
113
+ console.log(result.code) // "123456"
114
+ }
115
+ ```
116
+
117
+ ## Cloud API (mails.dev)
118
+
119
+ For agents that need email without local CLI setup. Pay per use with USDC via x402.
120
+
121
+ ```
122
+ Base URL: https://api.mails.dev
123
+
124
+ POST /v1/send Send an email ($0.001/email)
125
+ GET /v1/inbox?to=... List received emails (free)
126
+ GET /v1/code?to=... Wait for verification code (free)
127
+ ```
128
+
129
+ ### Send via API
130
+
131
+ ```bash
132
+ curl -X POST https://api.mails.dev/v1/send \
133
+ -H "Content-Type: application/json" \
134
+ -d '{
135
+ "from": "agent@mails.dev",
136
+ "to": ["user@example.com"],
137
+ "subject": "Hello",
138
+ "text": "Sent via mails.dev cloud API"
139
+ }'
140
+ ```
141
+
142
+ If no payment header is present, the API returns HTTP 402 with payment instructions.
143
+ Attach an `X-PAYMENT` header with a signed USDC payment to complete the request.
144
+
145
+ ### Query Inbox
146
+
147
+ ```bash
148
+ # List inbox (free)
149
+ curl "https://api.mails.dev/v1/inbox?to=myagent@mails.dev&limit=10"
150
+
151
+ # Wait for verification code (free, long-poll up to 55s)
152
+ curl "https://api.mails.dev/v1/code?to=myagent@mails.dev&timeout=30"
153
+ ```
154
+
155
+ ## Self-Hosted Setup
156
+
157
+ For custom domains, run the interactive setup:
158
+
159
+ ```bash
160
+ mails setup
161
+ ```
162
+
163
+ This opens a browser-based wizard at `mails.dev/setup` that guides you through:
164
+ 1. Cloudflare API token configuration
165
+ 2. DNS record setup (MX, SPF, DKIM, DMARC)
166
+ 3. Email Worker deployment
167
+ 4. Send provider (Resend) configuration
168
+ 5. Storage provider selection
169
+
170
+ ## Storage Providers
171
+
172
+ ### SQLite (default)
173
+ Local database at `~/.mails/mails.db`. Zero config. Good for development and single-agent use.
174
+
175
+ ### db9.ai
176
+ Cloud PostgreSQL for agents. Enables multi-agent access to shared mailboxes.
177
+
178
+ ```bash
179
+ mails config set storage_provider db9
180
+ mails config set db9_token YOUR_TOKEN
181
+ mails config set db9_database_id YOUR_DB_ID
182
+ ```
183
+
184
+ ## Email Schema
185
+
186
+ All storage providers use this schema:
187
+
188
+ ```sql
189
+ CREATE TABLE emails (
190
+ id TEXT PRIMARY KEY,
191
+ mailbox TEXT NOT NULL,
192
+ from_address TEXT NOT NULL,
193
+ from_name TEXT DEFAULT '',
194
+ to_address TEXT NOT NULL,
195
+ subject TEXT DEFAULT '',
196
+ body_text TEXT DEFAULT '',
197
+ body_html TEXT DEFAULT '',
198
+ code TEXT,
199
+ headers TEXT DEFAULT '{}',
200
+ metadata TEXT DEFAULT '{}',
201
+ direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')),
202
+ status TEXT DEFAULT 'received' CHECK (status IN ('received', 'sent', 'failed', 'queued')),
203
+ received_at TEXT NOT NULL,
204
+ created_at TEXT NOT NULL
205
+ );
206
+ CREATE INDEX idx_emails_mailbox ON emails(mailbox, received_at DESC);
207
+ ```
208
+
209
+ ## Links
210
+
211
+ - Website: https://mails.dev
212
+ - npm: https://www.npmjs.com/package/mails
213
+ - GitHub: https://github.com/user/mails
package/.npmignore DELETED
@@ -1,7 +0,0 @@
1
- .DS_*
2
- node_modules
3
- *.sublime*
4
- psd
5
- thumb
6
- *.log
7
- demo