@agenticmail/openclaw 0.3.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/REFERENCE.md ADDED
@@ -0,0 +1,677 @@
1
+ # @agenticmail/openclaw — Technical Reference
2
+
3
+ Complete technical reference for the AgenticMail OpenClaw plugin. Covers every tool, the channel integration, sub-agent lifecycle, rate limiting, follow-up system, and all constants.
4
+
5
+ ---
6
+
7
+ ## Exports
8
+
9
+ ```typescript
10
+ export default function activate(api: any): void
11
+ ```
12
+
13
+ Single default export. Called by OpenClaw when the plugin loads.
14
+
15
+ ---
16
+
17
+ ## Plugin Manifest
18
+
19
+ **File:** `openclaw.plugin.json`
20
+
21
+ ```json
22
+ {
23
+ "id": "agenticmail",
24
+ "displayName": "AgenticMail",
25
+ "version": "0.2.0",
26
+ "description": "Full email channel + tools for AI agents",
27
+ "channels": ["mail"],
28
+ "configSchema": {
29
+ "apiUrl": { "type": "string", "default": "http://127.0.0.1:3100" },
30
+ "apiKey": { "type": "string", "required": true },
31
+ "masterKey": { "type": "string" }
32
+ },
33
+ "requires": { "bins": ["docker"] }
34
+ }
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Configuration
40
+
41
+ ### ToolContext
42
+
43
+ ```typescript
44
+ interface ToolContext {
45
+ config: {
46
+ apiUrl: string; // Default: 'http://127.0.0.1:3100'
47
+ apiKey: string; // Agent API key (required)
48
+ masterKey?: string; // Master admin key (optional)
49
+ };
50
+ ownerName?: string; // Resolved from OpenClaw agent config
51
+ }
52
+ ```
53
+
54
+ ### Config Resolution
55
+
56
+ 1. `api?.getConfig?.()` or `{}`
57
+ 2. `api?.pluginConfig` or step 1 result
58
+ 3. Manifest defaults
59
+
60
+ ### Owner Name Resolution
61
+
62
+ Extracted from OpenClaw agent config: `api?.config?.agents?.list` → `defaultAgent?.identity?.name` or first agent's name.
63
+
64
+ ---
65
+
66
+ ## API Request Function
67
+
68
+ ```typescript
69
+ async function apiRequest(
70
+ ctx: ToolContext,
71
+ method: string,
72
+ path: string,
73
+ body?: unknown,
74
+ useMasterKey = false,
75
+ timeoutMs = 30_000
76
+ ): Promise<any>
77
+ ```
78
+
79
+ - Base URL: `${ctx.config.apiUrl}/api/agenticmail${path}`
80
+ - Auth: `Authorization: Bearer ${useMasterKey ? ctx.config.masterKey : ctx.config.apiKey}`
81
+ - Throws if required key not configured
82
+ - Timeout: `AbortSignal.timeout(timeoutMs)` — 30 seconds default
83
+ - Error: `AgenticMail API error {status}: {text}`
84
+ - Response: JSON if Content-Type includes `application/json`, else `null`
85
+
86
+ ---
87
+
88
+ ## Sub-Agent Identity System
89
+
90
+ ### SubagentAccount
91
+
92
+ ```typescript
93
+ interface SubagentAccount {
94
+ id: string;
95
+ name: string;
96
+ email: string;
97
+ apiKey: string;
98
+ parentEmail: string; // Coordinator's email (for auto-CC)
99
+ createdAt: number; // ms since epoch
100
+ }
101
+ ```
102
+
103
+ ### AgentIdentity Registry
104
+
105
+ ```typescript
106
+ interface AgentIdentity {
107
+ apiKey: string;
108
+ parentEmail: string;
109
+ }
110
+
111
+ registerAgentIdentity(name: string, apiKey: string, parentEmail: string): void
112
+ unregisterAgentIdentity(name: string): void
113
+ setLastActivatedAgent(name: string): void
114
+ clearLastActivatedAgent(name: string): void
115
+ ```
116
+
117
+ ### Context Resolution (4-path hierarchy)
118
+
119
+ 1. **Direct injection:** `params._agentApiKey` (from tool factory)
120
+ 2. **Raw key:** `params._auth` (from prepend context)
121
+ 3. **Agent name:** `params._account` → lookup in identity registry → fallback to API lookup via master key
122
+ 4. **Auto-detect:** `lastActivatedAgent` (zero-cooperation fallback)
123
+
124
+ ### Auto-CC
125
+
126
+ When a sub-agent sends an inter-agent email (`@localhost`), the parent coordinator is automatically added to CC. External emails skip auto-CC. Deduplication prevents adding the parent if already in To or CC.
127
+
128
+ ---
129
+
130
+ ## Inter-Agent Rate Limiting
131
+
132
+ ### Configuration
133
+
134
+ | Parameter | Value |
135
+ |-----------|-------|
136
+ | `WARN_THRESHOLD` | 3 unanswered messages |
137
+ | `BLOCK_THRESHOLD` | 5 unanswered messages |
138
+ | `WINDOW_MAX` | 10 messages per window |
139
+ | `WINDOW_MS` | 300,000ms (5 minutes) |
140
+ | `COOLDOWN_MS` | 120,000ms (2 minutes) |
141
+ | `TRACKER_GC_INTERVAL_MS` | 600,000ms (10 minutes) |
142
+ | `TRACKER_STALE_MS` | 1,800,000ms (30 minutes) |
143
+
144
+ ### MessageRecord
145
+
146
+ ```typescript
147
+ interface MessageRecord {
148
+ unanswered: number; // Consecutive unanswered count
149
+ sentTimestamps: number[]; // Timestamps within window
150
+ lastSentAt: number;
151
+ lastReplyAt: number;
152
+ }
153
+ ```
154
+
155
+ ### Functions
156
+
157
+ | Function | Description |
158
+ |----------|-------------|
159
+ | `checkRateLimit(from, to)` | Returns `{allowed, warning?}` |
160
+ | `recordSentMessage(from, to)` | Increments unanswered, adds timestamp |
161
+ | `recordInboundAgentMessage(from, to)` | Resets unanswered count |
162
+
163
+ ---
164
+
165
+ ## Outbound Security Guard
166
+
167
+ ### Scan Function
168
+
169
+ ```typescript
170
+ function scanOutbound(
171
+ to: string | string[],
172
+ subject?: string,
173
+ text?: string,
174
+ html?: string,
175
+ attachments?: Array<{ filename?: string }>
176
+ ): OutboundScanResultInline
177
+ ```
178
+
179
+ Returns: `{ warnings: McpOutboundWarning[], blocked: boolean, summary: string }`
180
+
181
+ Skips scanning if all recipients are `@localhost`.
182
+
183
+ ### Detection Rules (38+)
184
+
185
+ **PII (13 rules):**
186
+
187
+ | Rule ID | Severity | Description |
188
+ |---------|----------|-------------|
189
+ | `ob_ssn` | HIGH | SSN pattern `\d{3}-\d{2}-\d{4}` |
190
+ | `ob_ssn_obfuscated` | HIGH | Obfuscated SSN variants |
191
+ | `ob_credit_card` | HIGH | Credit card numbers |
192
+ | `ob_phone` | MEDIUM | Phone number patterns |
193
+ | `ob_bank_routing` | HIGH | Routing/account numbers |
194
+ | `ob_drivers_license` | HIGH | Driver's license patterns |
195
+ | `ob_dob` | MEDIUM | Date of birth with keywords |
196
+ | `ob_passport` | HIGH | Passport numbers |
197
+ | `ob_tax_id` | HIGH | EIN/TIN patterns |
198
+ | `ob_itin` | HIGH | ITIN patterns |
199
+ | `ob_medicare` | HIGH | Medicare/Medicaid IDs |
200
+ | `ob_immigration` | HIGH | Immigration A-numbers |
201
+ | `ob_pin` | MEDIUM | PIN codes |
202
+
203
+ **Financial (5 rules):**
204
+
205
+ | Rule ID | Severity | Description |
206
+ |---------|----------|-------------|
207
+ | `ob_security_qa` | MEDIUM | Security Q&A patterns |
208
+ | `ob_iban` | HIGH | IBAN patterns |
209
+ | `ob_swift` | MEDIUM | SWIFT/BIC codes |
210
+ | `ob_crypto_wallet` | HIGH | BTC/ETH/XMR wallet addresses |
211
+ | `ob_wire_transfer` | HIGH | Wire transfer instructions |
212
+
213
+ **Credentials (16 rules):**
214
+
215
+ | Rule ID | Severity | Description |
216
+ |---------|----------|-------------|
217
+ | `ob_api_key` | HIGH | API key patterns |
218
+ | `ob_aws_key` | HIGH | `AKIA[A-Z0-9]{16}` |
219
+ | `ob_password_value` | HIGH | Password field patterns |
220
+ | `ob_private_key` | HIGH | PEM private key headers |
221
+ | `ob_bearer_token` | HIGH | Bearer token patterns |
222
+ | `ob_connection_string` | HIGH | DB connection strings |
223
+ | `ob_github_token` | HIGH | GitHub token patterns |
224
+ | `ob_stripe_key` | HIGH | Stripe key patterns |
225
+ | `ob_jwt` | HIGH | JWT token patterns |
226
+ | `ob_webhook_url` | HIGH | Slack/Discord webhook URLs |
227
+ | `ob_env_block` | HIGH | Consecutive ENV variable lines |
228
+ | `ob_seed_phrase` | HIGH | Crypto seed/recovery phrases |
229
+ | `ob_2fa_codes` | HIGH | 2FA backup codes |
230
+ | `ob_credential_pair` | HIGH | Username+password pairs |
231
+ | `ob_oauth_token` | HIGH | OAuth tokens |
232
+ | `ob_vpn_creds` | HIGH | VPN credentials |
233
+
234
+ **System Internals (3 rules):**
235
+
236
+ | Rule ID | Severity | Description |
237
+ |---------|----------|-------------|
238
+ | `ob_private_ip` | MEDIUM | Private IP ranges |
239
+ | `ob_file_path` | MEDIUM | File paths (/home, /Users, C:\\) |
240
+ | `ob_env_variable` | MEDIUM | Environment variable assignments |
241
+
242
+ **Owner Privacy (2 rules):**
243
+
244
+ | Rule ID | Severity | Description |
245
+ |---------|----------|-------------|
246
+ | `ob_owner_info` | HIGH | Owner's personal info |
247
+ | `ob_personal_reveal` | HIGH | Agent's creator/operator |
248
+
249
+ **Attachment Risk:**
250
+
251
+ | Risk Level | Extensions |
252
+ |------------|------------|
253
+ | HIGH (keys) | `.pem`, `.key`, `.p12`, `.pfx`, `.env`, `.credentials`, `.keystore`, `.jks`, `.p8` |
254
+ | MEDIUM (data) | `.db`, `.sqlite`, `.sqlite3`, `.sql`, `.csv`, `.tsv`, `.json`, `.yml`, `.yaml`, `.conf`, `.config`, `.ini` |
255
+ | HIGH (exec) | `.exe`, `.bat`, `.cmd`, `.ps1`, `.sh`, `.msi`, `.scr`, `.com`, `.vbs`, `.js`, `.wsf`, `.hta`, `.cpl`, `.jar`, `.app`, `.dmg`, `.run` |
256
+ | MEDIUM (archive) | `.zip`, `.rar`, `.7z`, `.tar`, `.gz`, `.bz2`, `.xz`, `.cab`, `.iso` |
257
+ | CRITICAL | Double extensions (e.g., `.pdf.exe`) |
258
+
259
+ ---
260
+
261
+ ## Tool Definitions (54 tools)
262
+
263
+ ### Tool Registration
264
+
265
+ Tools are registered as factories — OpenClaw calls the factory per-session with `{sessionKey, ...}`. The sub-agent API key is injected at execution time through the 4-path context resolution hierarchy.
266
+
267
+ ### Response Format
268
+
269
+ ```typescript
270
+ {
271
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
272
+ details: result
273
+ }
274
+ ```
275
+
276
+ ### agenticmail_send
277
+
278
+ | Field | Type | Required | Description |
279
+ |-------|------|----------|-------------|
280
+ | `to` | string | Yes | Recipient |
281
+ | `subject` | string | Yes | Subject line |
282
+ | `text` | string | No | Plain text body |
283
+ | `html` | string | No | HTML body |
284
+ | `cc` | string | No | CC recipients |
285
+ | `inReplyTo` | string | No | Message-ID for threading |
286
+ | `references` | array | No | Message-ID chain |
287
+ | `attachments` | array | No | `{filename, content, contentType, encoding}` |
288
+
289
+ Auto-CC: Parent coordinator added to CC for inter-agent emails.
290
+ Outbound guard: Runs inline scan. If blocked, schedules follow-up reminders.
291
+
292
+ ### agenticmail_inbox
293
+
294
+ | Field | Type | Default | Range |
295
+ |-------|------|---------|-------|
296
+ | `limit` | number | 20 | 1–100 |
297
+ | `offset` | number | 0 | 0+ |
298
+
299
+ ### agenticmail_read
300
+
301
+ | Field | Type | Required | Default |
302
+ |-------|------|----------|---------|
303
+ | `uid` | number | Yes | — |
304
+ | `folder` | string | No | INBOX |
305
+
306
+ Response includes `_securityWarnings` and `_securityAdvisory` for external emails.
307
+
308
+ ### agenticmail_search
309
+
310
+ | Field | Type | Description |
311
+ |-------|------|-------------|
312
+ | `from` | string | Sender filter |
313
+ | `to` | string | Recipient filter |
314
+ | `subject` | string | Subject filter |
315
+ | `text` | string | Body text search |
316
+ | `since` | string | ISO 8601 (after) |
317
+ | `before` | string | ISO 8601 (before) |
318
+ | `seen` | boolean | Read status |
319
+ | `searchRelay` | boolean | Also search Gmail/Outlook |
320
+
321
+ ### agenticmail_import_relay
322
+
323
+ | Field | Type | Required |
324
+ |-------|------|----------|
325
+ | `uid` | number | Yes |
326
+
327
+ ### agenticmail_delete
328
+
329
+ | Field | Type | Required |
330
+ |-------|------|----------|
331
+ | `uid` | number | Yes |
332
+
333
+ ### agenticmail_reply
334
+
335
+ | Field | Type | Required | Default |
336
+ |-------|------|----------|---------|
337
+ | `uid` | number | Yes | — |
338
+ | `text` | string | Yes | — |
339
+ | `replyAll` | boolean | No | false |
340
+
341
+ Auto-quotes original, preserves In-Reply-To and References. Resets rate limiter on reply.
342
+
343
+ ### agenticmail_forward
344
+
345
+ | Field | Type | Required |
346
+ |-------|------|----------|
347
+ | `uid` | number | Yes |
348
+ | `to` | string | Yes |
349
+ | `text` | string | No |
350
+
351
+ Preserves original attachments.
352
+
353
+ ### Batch Operations
354
+
355
+ All require `uids: number[]` (non-empty array).
356
+
357
+ | Tool | Extra Fields | API Endpoint |
358
+ |------|-------------|-------------|
359
+ | `agenticmail_batch_read` | `folder?` | `POST /mail/batch/read` |
360
+ | `agenticmail_batch_delete` | `folder?` | `POST /mail/batch/delete` |
361
+ | `agenticmail_batch_mark_read` | `folder?` | `POST /mail/batch/seen` |
362
+ | `agenticmail_batch_mark_unread` | `folder?` | `POST /mail/batch/unseen` |
363
+ | `agenticmail_batch_move` | `from?`, `to` (required) | `POST /mail/batch/move` |
364
+
365
+ ### agenticmail_digest
366
+
367
+ | Field | Type | Default | Range |
368
+ |-------|------|---------|-------|
369
+ | `limit` | number | 20 | 1–50 |
370
+ | `offset` | number | 0 | 0+ |
371
+ | `folder` | string | INBOX | — |
372
+ | `previewLength` | number | 200 | 1–500 |
373
+
374
+ ### agenticmail_template_send
375
+
376
+ | Field | Type | Required |
377
+ |-------|------|----------|
378
+ | `id` | string | Yes |
379
+ | `to` | string | Yes |
380
+ | `variables` | object | No |
381
+ | `cc` | string | No |
382
+ | `bcc` | string | No |
383
+
384
+ ### Folder Management
385
+
386
+ | Tool | Fields | API |
387
+ |------|--------|-----|
388
+ | `agenticmail_folders` | (none) | `GET /mail/folders` |
389
+ | `agenticmail_list_folder` | `folder`, `limit?`, `offset?` | `GET /mail/folders/{folder}` |
390
+ | `agenticmail_create_folder` | `name` | `POST /mail/folders` |
391
+ | `agenticmail_move` | `uid`, `to`, `from?` | `POST /mail/messages/{uid}/move` |
392
+ | `agenticmail_mark_read` | `uid` | `POST /mail/messages/{uid}/seen` |
393
+ | `agenticmail_mark_unread` | `uid` | `POST /mail/messages/{uid}/unseen` |
394
+
395
+ ### Organization Tools
396
+
397
+ All action-based tools use `{ action: string, ... }` pattern.
398
+
399
+ **agenticmail_contacts:** Actions: `list`, `add` (email required, name optional), `delete` (id)
400
+
401
+ **agenticmail_tags:** Actions: `list`, `create` (name, color?), `delete` (id), `tag_message` (id, uid, folder?), `untag_message` (id, uid, folder?), `get_messages` (id), `get_message_tags` (uid)
402
+
403
+ **agenticmail_drafts:** Actions: `list`, `create` (to, subject, text), `update` (id, fields), `delete` (id), `send` (id)
404
+
405
+ **agenticmail_signatures:** Actions: `list`, `create` (name, text, isDefault?), `delete` (id)
406
+
407
+ **agenticmail_templates:** Actions: `list`, `create` (name, subject, text), `delete` (id)
408
+
409
+ **agenticmail_schedule:** Actions: `create` (to, subject, text, sendAt), `list`, `cancel` (id)
410
+
411
+ **agenticmail_rules:** Actions: `list`, `create` (name, conditions, actions, priority?), `delete` (id)
412
+ - Conditions: `from_contains`, `from_exact`, `subject_contains`, `subject_regex`, `to_contains`, `has_attachment`
413
+ - Actions: `move_to`, `mark_read`, `delete`, `add_tags`
414
+
415
+ ### Security Tools
416
+
417
+ **agenticmail_spam:** Actions: `list` (limit?, offset?), `report` (uid, folder?), `not_spam` (uid), `score` (uid, folder?)
418
+
419
+ **agenticmail_pending_emails:** Actions: `list`, `get` (id)
420
+ - `approve` and `reject` are **explicitly blocked** — returns error directing agent to notify owner
421
+
422
+ **agenticmail_cleanup** (master key): Actions: `list_inactive` (hours?), `cleanup` (hours?, dryRun?), `set_persistent` (agentId, persistent)
423
+
424
+ ### Inter-Agent Communication
425
+
426
+ **agenticmail_list_agents:** Returns `{agents: [{name, email, role}]}`. Falls back to master key list.
427
+
428
+ **agenticmail_message_agent:**
429
+ | Field | Type | Required |
430
+ |-------|------|----------|
431
+ | `agent` | string | Yes |
432
+ | `subject` | string | Yes |
433
+ | `text` | string | Yes |
434
+ | `priority` | "normal"\|"high"\|"urgent" | No |
435
+
436
+ Validates agent exists. Prevents self-messaging. Rate-limited. Priority prefixes subject with `[URGENT]` or `[HIGH]`.
437
+
438
+ **agenticmail_check_messages:** Fetches up to 10 unread messages. Tags as `[agent]` or `[external]`. Resets rate limiter.
439
+
440
+ **agenticmail_wait_for_email:**
441
+ | Field | Type | Default | Range |
442
+ |-------|------|---------|-------|
443
+ | `timeout` | number | 120 | 5–300 |
444
+
445
+ Uses SSE push with polling fallback. Returns email or task events.
446
+
447
+ ### Task Queue
448
+
449
+ **agenticmail_assign_task:** `{assignee, taskType?, payload?, expiresInSeconds?}`
450
+
451
+ **agenticmail_check_tasks:** `{direction: "incoming"|"outgoing", assignee?}`
452
+
453
+ **agenticmail_claim_task:** `{id}`
454
+
455
+ **agenticmail_submit_result:** `{id, result?}`
456
+
457
+ **agenticmail_call_agent:** `{target, task, payload?, timeout?}` — synchronous RPC, polls every 2s
458
+
459
+ ### Account Management
460
+
461
+ **agenticmail_whoami:** `GET /accounts/me`
462
+
463
+ **agenticmail_update_metadata:** `{metadata: object}` → `PATCH /accounts/me`
464
+
465
+ **agenticmail_create_account** (master): `{name, domain?, role?}` — also registers in identity registry
466
+
467
+ **agenticmail_delete_agent** (master): `{name, reason?}` → archives emails, generates deletion report
468
+
469
+ **agenticmail_deletion_reports** (master): `{id?}` — list all or get specific
470
+
471
+ ### Gateway Tools (all master key)
472
+
473
+ **agenticmail_status:** `GET /health`
474
+
475
+ **agenticmail_setup_guide:** Returns relay vs domain comparison
476
+
477
+ **agenticmail_setup_relay:** `{provider, email, password, smtpHost?, smtpPort?, imapHost?, imapPort?, agentName?, agentRole?, skipDefaultAgent?}`
478
+
479
+ **agenticmail_setup_domain:** `{cloudflareToken, cloudflareAccountId, domain?, purchase?, gmailRelay?}`
480
+
481
+ **agenticmail_setup_gmail_alias:** `{agentEmail, agentDisplayName?}`
482
+
483
+ **agenticmail_setup_payment:** No input
484
+
485
+ **agenticmail_purchase_domain:** `{keywords: string[], tld?}`
486
+
487
+ **agenticmail_gateway_status:** `GET /gateway/status`
488
+
489
+ **agenticmail_test_email:** `{to}` → `POST /gateway/test`
490
+
491
+ ---
492
+
493
+ ## Email Channel Integration
494
+
495
+ ### Channel Metadata
496
+
497
+ ```typescript
498
+ {
499
+ id: 'mail',
500
+ label: 'Email',
501
+ selectionLabel: 'Email (AgenticMail)',
502
+ capabilities: {
503
+ chatTypes: ['direct'],
504
+ media: true,
505
+ reply: true,
506
+ threads: true
507
+ }
508
+ }
509
+ ```
510
+
511
+ ### ResolvedMailAccount
512
+
513
+ ```typescript
514
+ {
515
+ accountId: string;
516
+ apiUrl: string;
517
+ apiKey: string;
518
+ watchMailboxes: string[]; // Default: ['INBOX']
519
+ pollIntervalMs: number; // Default: 30,000
520
+ enabled: boolean;
521
+ }
522
+ ```
523
+
524
+ ### Monitoring
525
+
526
+ 1. **SSE push** — connects to `GET /events` for IMAP IDLE-backed notifications
527
+ 2. **Polling fallback** — exponential backoff: 2s → 4s → 8s → 16s → 30s max
528
+ 3. **Processed UID tracking** — caps at 1000 (keeps latest 500)
529
+
530
+ ### Email Dispatch Pipeline
531
+
532
+ 1. New email detected (via SSE or poll)
533
+ 2. Build message context (OpenClaw format)
534
+ 3. Extract thread ID from `References[0]` or `messageId`
535
+ 4. Dispatch through `runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher`
536
+ 5. Mark email as read
537
+
538
+ ---
539
+
540
+ ## Follow-Up System
541
+
542
+ ### FollowUpEntry
543
+
544
+ ```typescript
545
+ interface FollowUpEntry {
546
+ pendingId: string;
547
+ recipient: string;
548
+ subject: string;
549
+ step: number; // 0-indexed within cycle
550
+ cycle: number; // Full cycles completed
551
+ nextFireAt: string; // ISO timestamp
552
+ createdAt: string; // ISO timestamp
553
+ sessionKey: string; // OpenClaw session key
554
+ apiUrl: string;
555
+ apiKey: string;
556
+ }
557
+ ```
558
+
559
+ ### Schedule
560
+
561
+ | Step | Delay |
562
+ |------|-------|
563
+ | 0 | 12 hours |
564
+ | 1 | 6 hours |
565
+ | 2 | 3 hours |
566
+ | 3 | 1 hour (final) |
567
+ | — | 3-day cooldown |
568
+ | 4+ | Cycle restarts |
569
+
570
+ ### Persistence
571
+
572
+ Stored at `${stateDir}/agenticmail-followups.json`:
573
+ ```json
574
+ { "version": 1, "entries": [...] }
575
+ ```
576
+
577
+ Restored on startup. Entries >1 day overdue are skipped.
578
+
579
+ ### Delivery
580
+
581
+ Reminders delivered via `api.runtime.system.enqueueSystemEvent()`.
582
+
583
+ ### API
584
+
585
+ | Function | Description |
586
+ |----------|-------------|
587
+ | `initFollowUpSystem(api)` | Initialize (restore persisted state) |
588
+ | `scheduleFollowUp(pendingId, recipient, subject, sessionKey, apiUrl, apiKey)` | Start tracking |
589
+ | `cancelFollowUp(pendingId)` | Cancel specific |
590
+ | `cancelAllFollowUps()` | Cancel all |
591
+ | `activeFollowUpCount()` | Count tracked |
592
+ | `getFollowUpSummary()` | Get all entries summary |
593
+
594
+ ---
595
+
596
+ ## Lifecycle Hooks
597
+
598
+ ### before_agent_start
599
+
600
+ 1. Detect sub-agent session (`sessionKey.includes(':subagent:')`)
601
+ 2. Provision email account via `POST /accounts`
602
+ 3. Handle 409 conflict with UUID-suffixed retry
603
+ 4. Send auto-intro email in coordination thread
604
+ 5. Inject context: identity, mailbox requirement, security rules, unread mail summary
605
+
606
+ ### before_tool_call
607
+
608
+ 1. Inject sub-agent API key for `agenticmail_*` tools
609
+ 2. Inject pending email notifications from SSE watchers
610
+ 3. Capture `sessions_spawn` info (enforce min 10-minute timeout)
611
+
612
+ ### agent_end
613
+
614
+ 1. Cancel all follow-ups
615
+ 2. Remove from registries
616
+ 3. Stop SSE watcher
617
+ 4. Delay 5 seconds (grace period)
618
+ 5. Delete account via `DELETE /accounts/{id}` with master key
619
+
620
+ ---
621
+
622
+ ## Health Monitor Service
623
+
624
+ ```typescript
625
+ {
626
+ id: 'agenticmail-monitor',
627
+ start(): validates API connectivity, logs agent name and email
628
+ stop(): logs shutdown
629
+ }
630
+ ```
631
+
632
+ ---
633
+
634
+ ## Constants Summary
635
+
636
+ | Constant | Value | Description |
637
+ |----------|-------|-------------|
638
+ | `MIN_SUBAGENT_TIMEOUT_S` | 600 (10 min) | Minimum sub-agent session timeout |
639
+ | `SUBAGENT_GC_INTERVAL_MS` | 900,000 (15 min) | Sub-agent garbage collection interval |
640
+ | `SUBAGENT_MAX_AGE_MS` | 7,200,000 (2 hr) | Max sub-agent account age |
641
+ | `CLEANUP_GRACE_MS` | 5,000 (5 sec) | Grace period before account deletion |
642
+ | `RATE_LIMIT.WARN_THRESHOLD` | 3 | Unanswered messages before warning |
643
+ | `RATE_LIMIT.BLOCK_THRESHOLD` | 5 | Unanswered messages before blocking |
644
+ | `RATE_LIMIT.WINDOW_MAX` | 10 | Max messages per window |
645
+ | `RATE_LIMIT.WINDOW_MS` | 300,000 (5 min) | Rate limit window |
646
+ | `RATE_LIMIT.COOLDOWN_MS` | 120,000 (2 min) | Cooldown after block |
647
+ | `TRACKER_GC_INTERVAL_MS` | 600,000 (10 min) | Rate limiter GC interval |
648
+ | `TRACKER_STALE_MS` | 1,800,000 (30 min) | Rate limiter stale threshold |
649
+ | `SSE_INITIAL_DELAY_MS` | 2,000 | Initial SSE reconnect backoff |
650
+ | `SSE_MAX_DELAY_MS` | 30,000 | Max SSE reconnect backoff |
651
+ | `apiRequest timeout` | 30,000 | Default API timeout |
652
+ | `HEARTBEAT_INTERVAL_MS` | 300,000 (5 min) | Pending email check interval |
653
+ | Follow-up cooldown | 259,200,000 (3 days) | Between follow-up cycles |
654
+ | Follow-up steps | [12h, 6h, 3h, 1h] | Escalating reminder delays |
655
+ | `processedUids` cap | 1,000 (keep 500) | Channel UID tracking limit |
656
+ | `pollIntervalMs` default | 30,000 | Channel polling interval |
657
+
658
+ ---
659
+
660
+ ## Skill Files
661
+
662
+ ```
663
+ skill/
664
+ ├── SKILL.md # Main skill definition (injected into prompt)
665
+ ├── references/
666
+ │ ├── api-reference.md # API endpoint reference
667
+ │ └── configuration.md # Config guide
668
+ └── scripts/
669
+ ├── health-check.sh # Server health check
670
+ └── setup.sh # Setup helper
671
+ ```
672
+
673
+ ---
674
+
675
+ ## License
676
+
677
+ [MIT](./LICENSE) - Ope Olatunji ([@ope-olatunji](https://github.com/ope-olatunji))
@@ -0,0 +1,12 @@
1
+ declare function activate(api: any): void;
2
+ /**
3
+ * OpenClaw plugin module export.
4
+ * Must export an object with `id` and `register` — OpenClaw reads `id` for identification
5
+ * and calls `register(api)` during plugin activation.
6
+ */
7
+ declare const _default: {
8
+ id: string;
9
+ register: typeof activate;
10
+ };
11
+
12
+ export { _default as default };