@framers/agentos-ext-channel-email 0.4.4 → 0.6.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.
Files changed (39) hide show
  1. package/README.md +74 -9
  2. package/dist/EmailService.d.ts +286 -9
  3. package/dist/EmailService.d.ts.map +1 -1
  4. package/dist/EmailService.js +592 -35
  5. package/dist/EmailService.js.map +1 -1
  6. package/dist/attachments.d.ts +118 -0
  7. package/dist/attachments.d.ts.map +1 -0
  8. package/dist/attachments.js +294 -0
  9. package/dist/attachments.js.map +1 -0
  10. package/dist/headers.d.ts +117 -0
  11. package/dist/headers.d.ts.map +1 -0
  12. package/dist/headers.js +285 -0
  13. package/dist/headers.js.map +1 -0
  14. package/dist/index.d.ts +11 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +17 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/messageHeaders.d.ts +8 -0
  19. package/dist/messageHeaders.d.ts.map +1 -1
  20. package/dist/messageHeaders.js +2 -0
  21. package/dist/messageHeaders.js.map +1 -1
  22. package/dist/preferences/RecipientCapability.d.ts +3 -2
  23. package/dist/preferences/RecipientCapability.d.ts.map +1 -1
  24. package/dist/preferences/RecipientCapability.js +2 -1
  25. package/dist/preferences/RecipientCapability.js.map +1 -1
  26. package/dist/tools/messages.d.ts +32 -0
  27. package/dist/tools/messages.d.ts.map +1 -0
  28. package/dist/tools/messages.js +28 -0
  29. package/dist/tools/messages.js.map +1 -0
  30. package/dist/tools/read.d.ts +4 -2
  31. package/dist/tools/read.d.ts.map +1 -1
  32. package/dist/tools/read.js +10 -4
  33. package/dist/tools/read.js.map +1 -1
  34. package/dist/tools/search.d.ts +4 -2
  35. package/dist/tools/search.d.ts.map +1 -1
  36. package/dist/tools/search.js +8 -3
  37. package/dist/tools/search.js.map +1 -1
  38. package/manifest.json +4 -3
  39. package/package.json +4 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @framers/agentos-ext-channel-email
2
2
 
3
- Email for AgentOS. `EmailService` sends mail through SMTP or Resend's HTTPS API, and reads, searches, extracts codes from and replies to mail through IMAP. The pack's factory wraps the service in a messaging-channel adapter and five tools for an agent. Two stores on a host's SQL database keep what a sender reads before a send: a suppression record and the tokens of unsubscribe links.
3
+ Email for AgentOS. `EmailService` sends mail through SMTP or Resend's HTTPS API, and reads, searches, extracts codes from, replies to, moves, labels, flags and deletes mail through IMAP. The pack's factory wraps the service in a messaging-channel adapter and five tools for an agent. Two stores on a host's SQL database keep what a sender reads before a send: a suppression record and the tokens of unsubscribe links.
4
4
 
5
5
  ## Installation
6
6
 
@@ -95,7 +95,7 @@ The subject goes out on one line through both transports: each line break in it,
95
95
 
96
96
  ## Suppressions and unsubscribe tokens
97
97
 
98
- Two stores a sender reads, each on the host's SQL database: `SqlSuppressionStore` keeps the addresses not to mail, each named by a hash of it, such as its SHA-256, and `SqlRecipientCapability` keeps opaque tokens, such as an unsubscribe link's, each naming a recipient and a scope. Both take [`@framers/sql-storage-adapter`](https://www.npmjs.com/package/@framers/sql-storage-adapter)'s adapter on SQLite or PostgreSQL, or any handle with its `run` and `get` (`?` placeholders, positional parameters), so the pack gains no dependency. The handle answers each `get` from the database: one that answers from a cache of its earlier reads, such as the adapter's deprecated `SupabaseAdapter` after `enableCache()`, can answer a row as it stood before a later write, so `find` could still answer a token after its `revoke`. A handle's `run` answers `changes`, the rows the statement wrote. A handle whose answer holds no such count, because it leaves `changes` out or gives something other than a whole number of 0 or more (the adapter's Capacitor handle gave an object before 0.6.14), still records, issues and revokes, but `suppress` then answers `false` and `revoke` 0, whatever they wrote.
98
+ Two stores a sender reads, each on the host's SQL database: `SqlSuppressionStore` keeps the addresses not to mail, each named by a hash of it, such as its SHA-256, and `SqlRecipientCapability` keeps opaque tokens, such as an unsubscribe link's, each naming a recipient and a scope. Both take [`@framers/sql-storage-adapter`](https://www.npmjs.com/package/@framers/sql-storage-adapter)'s adapter on SQLite or PostgreSQL, or any handle with its `run` and `get` (`?` placeholders, positional parameters), so the pack gains no dependency. The handle answers each `get` from the database: one that answers from a cache of its earlier reads can answer a row as it stood before a later write, so `find` could still answer a token after its `revoke`. Two such handles are the adapter's deprecated `SupabaseAdapter` after `enableCache()`, and an adapter a host builds on the adapter's `BaseStorageAdapter` at its default performance tier, `balanced`, which keeps each read for five seconds: give such an adapter the tier `accurate`, which keeps none. A handle's `run` answers `changes`, the rows the statement wrote. A handle whose answer holds no such count, because it leaves `changes` out or gives something other than a whole number of 0 or more (the adapter's Capacitor handle gave an object before 0.6.14), still records, issues and revokes, but `suppress` then answers `false` and `revoke` 0, whatever they wrote.
99
99
 
100
100
  ```ts
101
101
  import { createHash } from 'node:crypto';
@@ -127,7 +127,7 @@ const grant = await tokens.find(token); // { recipient: 'account-1', scope: 'wee
127
127
 
128
128
  `suppress(scope, addressHash, reason, day)` records an address in a scope with its reason and its day (`YYYY-MM-DD`), and no end: the store has no call that lifts an address, so an operator lifts one by deleting its row through the same handle, as in `await db.run('DELETE FROM mail_suppressions WHERE scope = ? AND address_hash = ?', [DEFAULT_SUPPRESSION_SCOPE, addressHash])` for the example's prefix `mail`. The `?` placeholders are the adapter's, which binds the values given and on PostgreSQL writes `$1` and `$2` in their place. In a console, write the scope and the hash as quoted literals instead: PostgreSQL reads a `?` as an operator and refuses the statement, and the sqlite3 shell binds a `?` to NULL, so the statement deletes nothing. The reason is `bounce`, `complaint`, `suppressed` (the provider refused a message because its own list held the address) or `manual`. A second record of the address in the scope writes nothing and keeps the first, so a provider's repeated event is harmless; `suppress` answers `true` only for the call that recorded it, and two hosts recording one address at once leave one row. `isSuppressed(scope, addressHash)` answers for one scope. A scope is one list, and a host that keeps one passes `DEFAULT_SUPPRESSION_SCOPE` (`mail`) everywhere. `addressHash` is the address's hash as 64 lower-case hexadecimal characters, such as its SHA-256, which the host computes the same way wherever it reads or writes the record. Any other value, an address among them, throws a `TypeError` that holds none of it, so a mistake stores nothing and is never answered as not suppressed. The store takes any 64 lower-case hexadecimal characters, so the host chooses the hash. A plain SHA-256, as in the example, lets anyone who holds the table check an address they guess against it; a host that wants the table to tell nothing of an address it does not already hold computes HMAC-SHA256 under a secret key of its own instead, the same way at every reader and writer. `SUPPRESSION_REASONS` lists the four reasons, and it is frozen, since `suppress` checks a reason against it.
129
129
 
130
- `issue(recipient, scope, nonce, key)` answers base64url of HMAC-SHA256 over the nonce under the key, 43 characters, and stores the token's SHA-256 beside the recipient and the scope: the token carries neither, and the table holds nothing it can be recovered from. The same nonce under the same key gives the same token, so each message takes a nonce of its own, such as its id. Issuing it again for the same recipient, scope and expiry writes nothing; for another recipient, scope or expiry it throws, and the first token keeps all three. The key is 32 random bytes or more, given as a `Uint8Array` or a `Buffer` and kept secret, such as `crypto.randomBytes(32)` stored with the host's other secrets; the store cannot judge how random a key is. A key made in another realm, such as a `vm` context or a test runner's sandbox, is taken as the same bytes made here. A different key gives different tokens for the same nonces, and the same key the same tokens, whatever the prefix: the prefix keeps two hosts' tables apart, not their tokens. Two hosts on one database that share a key and use one nonce draw one token, and each one's `find` answers its own row for it, so a link one host sent could act on the other host's recipient. Each host takes a key of its own, or nonces no other host uses, such as its own name before each message id. `find(token)` answers the recipient and the scope, or `null` for a token that is malformed, unknown, revoked or past its expiry: it looks the token up by its SHA-256 and compares in constant time. `revoke(recipient)` ends every token of the recipient, in every scope, and answers how many it ended. Each revoked row stays, marked with the time of the revoke, so `issue` refuses a revoked token's nonce for every recipient, scope and expiry: a revoked link never works again and never comes to name someone else. A message that needs a working token after a revoke takes a new nonce, and the token it gets works. A token has no expiry unless `issue`'s options set `expiresAt`, in epoch milliseconds.
130
+ `issue(recipient, scope, nonce, key)` answers base64url of HMAC-SHA256 over the nonce under the key, 43 characters, and stores the token's SHA-256 beside the recipient and the scope: the token carries neither, and the table holds nothing it can be recovered from. The same nonce under the same key gives the same token, so each message takes a nonce of its own, such as its id. Issuing it again for the same recipient, scope and expiry writes nothing; for another recipient, scope or expiry it throws, and the first token keeps all three. The key is 32 random bytes or more, given as a `Uint8Array` or a `Buffer` and kept secret, such as `crypto.randomBytes(32)` stored with the host's other secrets; the store cannot judge how random a key is. A key made in another realm, such as a `vm` context or a test runner's sandbox, is taken as the same bytes made here. A different key gives different tokens for the same nonces, and the same key the same tokens, whatever the prefix: the prefix keeps two hosts' tables apart, not their tokens. Two hosts on one database that share a key and use one nonce draw one token, and each one's `find` answers its own row for it, so a link one host sent could act on the other host's recipient. Each host takes a key of its own, or nonces no other host uses, such as its own name before each message id. `find(token)` answers the recipient and the scope, or `null` for a token that is malformed, unknown, revoked or past its expiry: it looks the token up by its SHA-256 and compares in constant time. `revoke(recipient)` ends every token of the recipient, in every scope, and answers how many it marked: each of the recipient's tokens not revoked before, one past its expiry among them. Each revoked row stays, marked with the time of the revoke, so `issue` refuses a revoked token's nonce for every recipient, scope and expiry: a revoked link never works again and never comes to name someone else. A message that needs a working token after a revoke takes a new nonce, and the token it gets works. A token has no expiry unless `issue`'s options set `expiresAt`, in epoch milliseconds.
131
131
 
132
132
  `suppressionStoreDdl(prefix)` makes the table `<prefix>_suppressions` (`scope`, `address_hash`, `reason`, `suppressed_on`), `recipientCapabilityDdl(prefix)` makes `<prefix>_capabilities` (`token_hash`, `recipient`, `scope`, `expires_at`, `revoked_at`) with its index on the recipient, and `emailPreferencesDdl(prefix)` makes both. The prefix is a lower-case letter and at most 39 lower-case letters, digits or underscores; any other value throws a `RangeError` from the DDL functions and both constructors. The prefix keeps two hosts' tables apart on one database. On SQLite, take a prefix other than `sqlite` and those beginning `sqlite_`: their tables would begin `sqlite_`, names SQLite keeps for its own tables, so its `CREATE TABLE` refuses them ([CREATE TABLE](https://www.sqlite.org/lang_createtable.html)) and every call of a store on such a prefix then fails on the missing table. PostgreSQL makes them. The statements are `CREATE ... IF NOT EXISTS`, so a migration can run them again, and they run on SQLite and PostgreSQL, from one process at a time: PostgreSQL does not promise that two `CREATE TABLE IF NOT EXISTS` of one new table at once both succeed, and one of them can fail on a duplicate key in `pg_type` (pgsql-hackers, 9 October 2018: [the report](https://www.postgresql.org/message-id/93336f06-86c2-71b6-abe3-0d42cc614b2b%40postgrespro.ru) and [the reply](https://www.postgresql.org/message-id/22700.1539093909%40sss.pgh.pa.us)). A host whose processes start together runs them in a migration, such as the adapter's `runSchemaMigrations` (0.7.2 or later), which takes an advisory lock on PostgreSQL. A token's row refuses its nonce for as long as it stays, revoked or not. A host that deletes a recipient's rows, by its own foreign key with `ON DELETE CASCADE` or by hand, never issues their nonces again: each message's nonce is one no other message ever had, such as a random id or a row id that is never reused. A host that cannot promise that calls `revoke` with each deletion and keeps the rows. Each store checks every argument before its first statement. A scope, a recipient and a nonce are strings of 1 to 512 UTF-16 code units (JavaScript's `length`, in which a character past U+FFFF counts two) with no U+0000, which PostgreSQL's text refuses, and no lone surrogate, which UTF-8 cannot encode, so a driver would store something else in its place: any other value throws a `TypeError` that holds none of it, and a name is stored as given on every backend. A database error passes through as it was thrown. A store made on the handle that the adapter's `transaction` gives its callback writes inside that transaction. On a server, open the database as the example does, with the adapter's `strict` option (0.6.15 or later): without it, a `url` that is unset opens a local SQLite database in its place, and the record a sender reads before each send would live there.
133
133
 
@@ -149,13 +149,13 @@ The wait is the answer's `retry-after` when it has one, else as many seconds as
149
149
 
150
150
  `readInbox`, `searchEmails`, `extractCodes` and `replyToEmail` read through IMAP: the `imap` account (`host`, `user` and `password`, with `port` and `secure`, by default TLS on port 993), or the SMTP account when `imap` is not given. With Resend, reading needs `imap`. The pack's factory reads the account from the secrets `email.imap.host`, `email.imap.user` and `email.imap.password`, or from `IMAP_HOST`, `IMAP_USER` and `IMAP_PASSWORD`.
151
151
 
152
- `readInbox(folder?, limit?)` gives the last `limit` messages of `folder`, newest first: INBOX and 20 when they are not given. `searchEmails({ query, folder?, since?, limit? })` finds the messages whose subject, body or From holds `query`, ignoring case, received on or after the day `since` names when it is given, and gives the last `limit` it finds (20 by default), newest first. Each search answers UIDs, and the messages are fetched by those UIDs, which need not follow the messages' sequence numbers once a message has been removed ([RFC 3501 section 2.3.1.1](https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1)).
152
+ `readInbox(folder?, limit?)` gives the last `limit` messages of `folder`, newest first: INBOX and 20 when they are not given; given one object instead, it can read after a UID ([the read surface](#the-read-surface)). `searchEmails({ query, folder?, since?, limit? })` finds the messages whose subject, body or From holds `query`, ignoring case, received on or after the day `since` names when it is given, and gives the last `limit` it finds (20 by default), newest first. Each search answers UIDs, and the messages are fetched by those UIDs, which need not follow the messages' sequence numbers once a message has been removed ([RFC 3501 section 2.3.1.1](https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1)).
153
153
 
154
154
  `limit` is a whole number of at least 1. `since` is an ISO 8601 date, such as `2026-10-15`, alone or followed by a time and an offset, such as `2026-10-15T09:30:00-05:00`. The search finds the messages the server received on the day the date names, as written, or later, the time of day disregarded ([RFC 3501 section 6.4.4](https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4)), on every server: the time and the offset are checked, then disregarded, and imapflow is given the day as text, which it sends as `SINCE` and that day. Given a `Date`, imapflow would search a server that announces `WITHIN` from that moment, to the second ([RFC 5032 section 2](https://www.rfc-editor.org/rfc/rfc5032#section-2)), and any other server from its day in UTC.
155
155
 
156
156
  `limit` and `since` count as not given when they are `undefined` or `null`, and `since` also when it is empty. Any other `limit` or `since` throws an `Error` before any mail is read: the fetch would otherwise name a message past the last one, which a server answers `BAD`, and imapflow leaves a date it cannot read out of the search, which then answers mail of every date. The refused `since` values include a number, text in another form such as `October 15, 2026`, which `Date` reads by guesswork and in the local time zone, and a day its month lacks such as `2026-02-30`, which `Date` reads as 2 March.
157
157
 
158
- Each message read carries its `messageId`, the first address of its `from` and of its `to`, its `subject`, `date`, `body`, `flags` and `uid`. `date` is the message's Date field in ISO 8601, or the empty string when it has none or the field does not read as a date, so one such message never fails the read.
158
+ Each message read carries its `messageId`, the first address of its `from` and of its `to`, its `subject`, `date`, `body`, `flags` and `uid`, and the fields [the read surface](#the-read-surface) lists. `date` is the message's Date field in ISO 8601, or the empty string when it has none or the field does not read as a date, so one such message never fails the read.
159
159
 
160
160
  `extractCodes(messageId, pattern?)` and `replyToEmail(messageId, body, html?)` name an INBOX message by its Message-ID, with or without its angle brackets: `<id@example.com>` and `id@example.com` name the same message. An IMAP search matches every Message-ID that holds the text ([RFC 3501 section 6.4.4](https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4)), so each searches for the id in angle brackets and takes only the message whose own id is that one. `extractCodes` answers no codes for an id that is not one message id or that no message has.
161
161
 
@@ -169,9 +169,72 @@ A reply goes out through the transport that sends:
169
169
 
170
170
  `replyToEmail` throws an `Error` that holds none of the id when the id is not one message id of printable US-ASCII with no space, or is too long for one line of `In-Reply-To`, before the original is read; and when no message in INBOX has the id, or the original names no address to reply to, before anything is sent.
171
171
 
172
+ ## The read surface
173
+
174
+ A host that files the mail a mailbox receives, such as a support desk, reads by UID and acts on each message by its UID:
175
+
176
+ ```ts
177
+ import { EmailMailboxError } from '@framers/agentos-ext-channel-email';
178
+
179
+ // The last UID filed and the folder's UIDVALIDITY when it was read, which the host keeps.
180
+ let cursor = { uid: 0, uidValidity: '' };
181
+ const mails = await email.readInbox({ afterUid: cursor.uid, uidValidity: cursor.uidValidity, limit: 50, authServId: 'mx.example.com' });
182
+ for (const mail of mails) {
183
+ const picture = mail.attachments.find((attachment) => attachment.type === 'image/png');
184
+ const bytes = picture === undefined ? undefined : await picture.read(5_242_880);
185
+ // File mail.text and bytes, then remove the mailbox's copy; a copy already gone counts as removed.
186
+ await email.deleteMessage(mail.uid).catch((error: unknown) => {
187
+ if (!(error instanceof EmailMailboxError && error.code === 'gone')) throw error;
188
+ });
189
+ cursor = { uid: mail.uid, uidValidity: mail.uidValidity };
190
+ }
191
+ ```
192
+
193
+ `readInbox({ folder?, limit?, afterUid?, uidValidity?, maxBytes?, authServId? })` reads as `readInbox(folder, limit)` does: the last `limit` messages, newest first. With `afterUid` it gives the first `limit` messages whose UID is greater, oldest first, whatever their flags, so a cursor that keeps the last UID filed moves forward without skipping a message. A FETCH response the server sends unasked during a read, such as the flags another session changed ([RFC 3501 section 7.4.2](https://www.rfc-editor.org/rfc/rfc3501#section-7.4.2)), is no message of the read. It searches `UID <afterUid + 1>:*` and keeps the greater UIDs alone, since a range ending in `*` names the last message even when its UID is not greater ([RFC 3501 section 9](https://www.rfc-editor.org/rfc/rfc3501#section-9)). `afterUid` is a whole number from 0. A UID names the same message only while the folder's UIDVALIDITY holds ([RFC 3501 section 2.3.1.1](https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1)), so each message gives the folder's `uidValidity`, and given it beside `afterUid`, a read of a folder whose UIDVALIDITY is another starts from the folder's first message, since its UIDs were given anew. `uidValidity` is a whole number from 1 to 4,294,967,295, or its digits with no leading zero; `null` and the empty string count as not given. `searchEmails` takes `maxBytes` and `authServId` too, and gives the same fields.
194
+
195
+ Each message `readInbox` and `searchEmails` give carries, beside `messageId`, `from`, `to`, `subject`, `date`, `body`, `flags` and `uid`:
196
+
197
+ | Field | What it holds |
198
+ |---|---|
199
+ | `uidValidity` | The folder's UIDVALIDITY when the message was read, in decimal; the empty string when the server gave none. A host that keeps the last UID it read keeps this beside it |
200
+ | `key` | The key the mailbox keeps the mail by, the id the server gives where it gives one: the EMAILID ([RFC 8474](https://www.rfc-editor.org/rfc/rfc8474)) where the server offers OBJECTID, else Gmail's X-GM-MSGID where it offers X-GM-EXT-1. On any other server, the folder's name, a space and the Message-ID; else, for a message with no Message-ID, the folder's name, a space, its UIDVALIDITY, a colon and its UID. A sender writes the Message-ID, so on such a server a message that copies another's Message-ID has its key: a host that keeps one mail a key compares the UID there too. A string to compare and store, not to parse |
201
+ | `recipients` | Every address the To, Cc and Delivered-To fields name, in lowercase, each once, in that order. A local part is in quotes, as [RFC 5322](https://www.rfc-editor.org/rfc/rfc5322#section-3.4.1) writes it, only when it holds white space, a double quote or another special but the dot, such as `"support team"@example.com`, whether the server gave it with its quoting removed or the field kept it |
202
+ | `inReplyTo`, `references` | The first message id In-Reply-To lists, or the empty string, and the ids References lists, each in angle brackets |
203
+ | `autoSubmitted` | The Auto-Submitted keyword ([RFC 3834](https://www.rfc-editor.org/rfc/rfc3834#section-5)) in lowercase, such as `auto-replied`; `no` when the field is absent |
204
+ | `precedence` | The Precedence keyword in lowercase, such as `bulk` or `list`; the empty string when the field is absent |
205
+ | `autoreply` | Whether the message carries an X-Autoreply or an X-Auto-Response-Suppress field |
206
+ | `isReport` | Whether its own type is `multipart/report` ([RFC 6522](https://www.rfc-editor.org/rfc/rfc6522)), as a delivery or read report's is |
207
+ | `authentication` | `dkim`, `spf` and `dmarc`, each result in lowercase, `dmarcFrom`, the domain the DMARC result names, and `alignedDomain`, read from an Authentication-Results field ([RFC 8601](https://www.rfc-editor.org/rfc/rfc8601)) as below; a result the field does not record reads `none` |
208
+ | `text`, `html` | The text parts, and the HTML parts, each decoded from its transfer encoding and charset and joined by a line break in their order, as a picture between two of them leaves them; each at most `maxBytes` bytes of UTF-8, cut on a character's edge. Of a `multipart/alternative`'s forms only the first that holds text, or HTML, counts. The empty string when the message has none |
209
+ | `truncated` | Whether the text or the HTML held more than `maxBytes` gives |
210
+ | `attachments` | Every other part, in the message's order: its `name`, `type`, `bytes` and `read(maxBytes?)` |
211
+
212
+ `authentication` comes from the topmost Authentication-Results field alone. Each host that checks a message adds its field at the top ([RFC 8601 section 2.1](https://www.rfc-editor.org/rfc/rfc8601#section-2.1) and [section 4](https://www.rfc-editor.org/rfc/rfc8601#section-4)), so the receiving host's field is the topmost and a field an earlier hop or the sender wrote sits below it; no field below the topmost is read. Give `authServId`, the authserv-id of the host that takes mail into the mailbox, such as `mx.google.com`, for any decision that trusts the results; a read that names none reads under the service's own, `EmailConfig.authServId`, when the configuration names one. The topmost field then counts only when it names that host, so a mail whose topmost field names another host, or none, reads `none` throughout. A field that names no authserv-id, as Outlook writes it (`Authentication-Results: spf=pass (sender IP is ...) smtp.mailfrom=example.com; dkim=pass ...`), is read from its first result, since an authserv-id is a token or a quoted string ([RFC 2045 section 5.1](https://www.rfc-editor.org/rfc/rfc2045#section-5.1)) and a bare `=` is part of neither; it counts with no `authServId` and never under one. The results are the topmost field as written. On a host that writes no field of its own, the topmost field is one the sender wrote, and `authServId` cannot tell the two apart; a host that copies a sender's text into its own field unescaped gives that text as results. A host that writes one field per test, as [RFC 8601 section 4](https://www.rfc-editor.org/rfc/rfc8601#section-4) allows, gives its topmost field's results alone, and the results its other fields record read `none`. So read a few real mails' fields on the mailbox before a decision trusts them. An `authServId` that is not text, holds a control character or is white space alone throws an `Error` before any mail is read, and `readHeaderFields` and `parseAuthenticationResults` throw the same; `null` and the empty string count as not given. Comments are left out, so a semicolon inside one splits nothing. `dkim` reads `pass` when any signature passed. `dmarc` is the receiving host's verdict for the domain `dmarcFrom` names, the domain of the From field that host read ([RFC 7489 section 11.1](https://www.rfc-editor.org/rfc/rfc7489#section-11.1)), so a host that trusts `dmarc` compares `dmarcFrom` with the domain of the message's `from`. `alignedDomain` is the From address's domain when a DKIM signature that passed was made for that domain or for a domain it ends in after a dot, its `header.d`, or the domain of its `header.i` where it names no `header.d`; it reads `none` otherwise, and when the From names no domain, as a group's or an absent From's does. A signature made for a domain below the From domain does not count, though the relaxed alignment of [RFC 7489 section 3.1.1](https://www.rfc-editor.org/rfc/rfc7489#section-3.1.1) counts it: finding a domain's organizational domain needs the public suffix list. A host that reads a message's header another way, such as through Gmail's API, reads the same fields with `readHeaderFields(headerBlock, { from, authServId })`, given the header's fields in their order, and the results alone with `parseAuthenticationResults`; given no `from` at all, these align with the domain the DMARC result names.
213
+
214
+ `maxBytes` is a whole number from 1 to 268,435,456, 262,144 when not given. A read fetches each message's envelope, flags, body structure and the header fields above through `BODY.PEEK`, which marks nothing seen, then its text and HTML parts by UID, each up to the bytes that can decode to what `maxBytes` leaves once the parts before it are read, so a large part is never fetched whole and a part past a filled bound is not fetched. A quoted-printable part written with runs of soft line breaks, which no encoder that keeps to the rules writes, can hold more than four bytes for each byte it decodes to; it reads as truncated, since a read fetches no more of a part than about four times the bound. `body` is `text`, or, for a message with no text part, its HTML with each tag written as a space.
215
+
216
+ An attachment is every part that is not the message's text or HTML: a part marked an attachment or named as a file, a part of any other type, and a message attached whole (`message/rfc822`), whose own parts are not listed. A text or HTML form a `multipart/alternative` does not take is neither. `bytes` is its size as the server holds it, in its transfer encoding, never less than its decoded bytes: about four thirds of them for base64. No read fetches an attachment's bytes. `read(maxBytes?)` fetches them by the message's UID over a connection of its own and resolves with the decoded bytes. It rejects with `EmailMailboxError` `too_large`, having fetched at most the bytes that could decode to the bound, when they pass `maxBytes`, a whole number from 1 to 268,435,456, 26,214,400 when not given, or when the part holds more bytes in its transfer encoding than decode to the bound from an encoder that keeps to its rules: twice the bound for base64, four times for quoted-printable. Since `bytes` counts the transfer encoding, a host that bounds an attachment's decoded size passes that bound to `read` and catches `too_large`. It rejects with `gone` when the folder no longer holds the message or its UIDVALIDITY changed since the read, and with `refused` when the server will not open the folder, as when it was deleted or renamed since the read.
217
+
218
+ ### Changes by UID
219
+
220
+ Each change acts on one message by its UID in `folder`, INBOX when not given, over a connection of its own:
221
+
222
+ - `moveTo(uid, destination, { folder? })` moves it with `UID MOVE` ([RFC 6851](https://www.rfc-editor.org/rfc/rfc6851)) where the server offers MOVE, else with `UID COPY`, the `\Deleted` flag and `UID EXPUNGE`; the original is touched only once the copy is made.
223
+ - `addLabel(uid, label, { folder? })` labels it through Gmail's `X-GM-LABELS` where the server offers X-GM-EXT-1, else copies it into the folder named `label`; the message stays where it is.
224
+ - `setFlags(uid, flags, { folder? })` replaces its flags with `flags`, such as `['\\Seen', '$Handled']`, with `UID STORE ... FLAGS`; an empty list clears them. A keyword goes in the spelling the folder's PERMANENTFLAGS give it, matched ignoring case, as IMAP reads a flag's name.
225
+ - `deleteMessage(uid, { folder? })` sets the `\Deleted` flag and expunges that UID alone with `UID EXPUNGE` ([RFC 4315](https://www.rfc-editor.org/rfc/rfc4315)), which leaves every other message marked deleted where it is.
226
+
227
+ A `UID STORE`, `UID COPY` or `UID MOVE` of a UID no message has succeeds having done nothing ([RFC 3501 section 6.4.8](https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8), which [RFC 6851 section 3.2](https://www.rfc-editor.org/rfc/rfc6851#section-3.2) extends to `UID MOVE`), so each change first searches for the UID, and after a store, a label or a copy whose answer cannot tell that it acted on the message, searches again. A change rejects with `EmailMailboxError`, whose `code` says why and whose message holds no UID, folder or address:
228
+
229
+ - `gone`: the folder holds no message with that UID, which comes before any other answer; or another session expunged it between the search and the change, as the COPYUID a server with UIDPLUS sends ([RFC 4315](https://www.rfc-editor.org/rfc/rfc4315#section-3)), or the search after the change, tells. A caller that wants the message gone, as after a delete that ran before a crash, counts it as done. A `UID MOVE` that another session's expunge overtakes, on a server that sends no COPYUID, resolves as done, the server having answered OK.
230
+ - `unsupported`, before anything changes: `deleteMessage` on a server without UIDPLUS, and `moveTo` on one with neither MOVE nor UIDPLUS, where `EXPUNGE` would remove every message marked deleted.
231
+ - `refused`: the server refused a command, as it does a destination or a label folder that does not exist, or would not open `folder`; imapflow's own error for that carries the server's text and the command, which name the folder, and the change's error carries neither. `setFlags` also refuses, before anything is stored, a flag the folder's PERMANENTFLAGS do not keep in any spelling, since imapflow would leave it out and a set left with no flag clears every flag.
232
+
233
+ A `uid` that is not a whole number from 1 to 4,294,967,295, a folder, destination or label that is empty or holds a control character, flags that are not a list of flag names, and `\Recent`, which the server alone sets, throw an `Error` before connecting.
234
+
172
235
  ## In an agent
173
236
 
174
- `createExtensionPack(context)` returns five tools, `emailSend`, `emailRead`, `emailSearch`, `emailExtractCodes` and `emailReply`, and the messaging channel `emailChannel`. Its options (`smtpHost`, `smtpUser`, `smtpPassword`, `smtpPort`, `smtpSecure`, `imapHost`, `imapUser`, `imapPassword`, `imapPort`, `imapSecure`, `resendApiKey`, `resendBaseUrl`, `from`) take precedence over the secrets and the environment variables named above. `manifest.json` lists the SMTP secrets as required, and the IMAP secrets and `email.resendApiKey` as optional.
237
+ `createExtensionPack(context)` returns five tools, `emailSend`, `emailRead`, `emailSearch`, `emailExtractCodes` and `emailReply`, and the messaging channel `emailChannel`. Its options (`smtpHost`, `smtpUser`, `smtpPassword`, `smtpPort`, `smtpSecure`, `imapHost`, `imapUser`, `imapPassword`, `imapPort`, `imapSecure`, `authServId`, `resendApiKey`, `resendBaseUrl`, `from`) take precedence over the secrets and the environment variables named above; `authServId` falls back to the secret `email.authServId`, then to `EMAIL_AUTH_SERV_ID`, and an empty value at any of them counts as none given, so the next one counts. `manifest.json` lists the SMTP secrets as required, and the IMAP secrets, `email.authServId` and `email.resendApiKey` as optional.
175
238
 
176
239
  A model names the tools' arguments and the channel's blocks, so `emailSend` and `emailChannel` pass on an attachment only as `content`, a `data:` address or, through Resend, an `http` or `https` address. They refuse any other before anything is sent, reading no file and fetching no address: the tool answers `{ success: false, error }`, the adapter throws an `Error`, and the message names the attachment's filename, never its path.
177
240
 
@@ -179,14 +242,16 @@ They send each attachment as they checked it: a copy of its own `filename`, `con
179
242
 
180
243
  The subject, the bodies and the message id a reply names must be text. Through SMTP, nodemailer reads the file or fetches the address an object body names into the message, and writes an object subject or message id marked `prepared` into the headers as it is. So `sendEmail` and `replyToEmail`, whoever calls them, throw a `TypeError` that names the field, never its value, before anything is sent on either transport: `subject`, `body` and the reply's `messageId` must be strings, and `html` a string when given (`null` leaves the HTML part out). `emailSend` and `emailReply` answer it as `{ success: false, error }`, and the adapter throws it. A reply whose message id is not one message id, or names no message in INBOX, is answered and thrown the same way, with the `Error` [reading mail](#reading-mail) describes.
181
244
 
182
- `emailRead`, `emailSearch` and `emailExtractCodes` answer a `limit`, a `since` or a `pattern` that [reading mail](#reading-mail) refuses as `{ success: false, error }`, before any mail is read, and a pattern that runs past its 250 ms the same way. `emailSearch` gives the last 20 messages it finds.
245
+ `emailRead`, `emailSearch` and `emailExtractCodes` answer a `limit`, a `since` or a `pattern` that [reading mail](#reading-mail) refuses as `{ success: false, error }`, before any mail is read, and a pattern that runs past its 250 ms the same way; `emailRead` answers a `folder` that is not a name the same way. `emailSearch` gives the last 20 messages it finds. `emailRead` and `emailSearch` answer plain data a model reads: each message without `html` and `text`, its text once as `body`, and each attachment's `name`, `type` and `bytes` without its `read()`. They read under the service's `authServId` and give each message's `authentication` only when the service names one: a model cannot name the host that takes mail into the mailbox, and a read that names no authserv-id counts the topmost Authentication-Results field whatever host it names, which on a host that writes no field of its own is one the sender wrote. An `authServId` in the configuration that is not text, holds a control character or is white space alone throws an `Error` when the pack is made.
183
246
 
184
247
  ## API
185
248
 
186
249
  | Export | What it is |
187
250
  |---|---|
188
251
  | `createExtensionPack(context)` | The pack's factory: the five tools and the channel adapter |
189
- | `EmailService` | Sends through SMTP or Resend's HTTPS API; reads, searches, extracts codes and replies through IMAP |
252
+ | `EmailService` | Sends through SMTP or Resend's HTTPS API; reads, searches, extracts codes, replies, moves, labels, flags and deletes through IMAP |
253
+ | `EmailMailboxError` | A change by UID, or an attachment's read, that failed, with its `code`: `gone`, `refused`, `unsupported` or `too_large` |
254
+ | `readHeaderFields(headerBlock, options?)`, `parseAuthenticationResults(headerBlock, options?)` | The fields a header block gives a read, and its Authentication-Results alone |
190
255
  | `ResendTransport` | Resend's send call, which `EmailService` uses when its configuration names `resend` |
191
256
  | `EmailApiError`, `retryWaitSeconds` | A refusal from Resend, and the wait before its retry (`null` for none) |
192
257
  | `EmailChannelAdapter` | The messaging-channel adapter |
@@ -195,7 +260,7 @@ The subject, the bodies and the message id a reply names must be text. Through S
195
260
  | `SqlRecipientCapability`, `recipientCapabilityDdl(prefix)` | Recipient capabilities on a SQL database, and their table's DDL |
196
261
  | `emailPreferencesDdl(prefix)` | Both tables' DDL |
197
262
  | `DEFAULT_SUPPRESSION_SCOPE`, `SUPPRESSION_REASONS` | The scope of a host that keeps one list (`mail`), and the reasons an address is suppressed, frozen |
198
- | `EmailConfig`, `SendEmailOptions`, `EmailMessage`, `SearchEmailOptions`, `ResendOptions`, `OutgoingEmail`, `OutgoingAttachment`, `EmailChannelOptions`, `ExtensionContext`, `ExtensionPack` | The types of the configuration, the messages, the options and the pack |
263
+ | `EmailConfig`, `SendEmailOptions`, `EmailMessage`, `EmailAttachment`, `SearchEmailOptions`, `ReadOptions`, `ReadInboxOptions`, `MailboxOptions`, `EmailMailboxErrorCode`, `EmailAuthentication`, `HeaderFieldOptions`, `HeaderFields`, `ResendOptions`, `OutgoingEmail`, `OutgoingAttachment`, `EmailChannelOptions`, `ExtensionContext`, `ExtensionPack` | The types of the configuration, the messages, the options and the pack |
199
264
  | `SuppressionStore`, `SuppressionReason`, `RecipientCapability`, `RecipientGrant`, `RecipientCapabilityIssueOptions`, `SqlRecipientCapabilityOptions`, `EmailPreferencesDb` | The stores' interfaces, their answers and options, and what they need of a database |
200
265
 
201
266
  ## License
@@ -3,8 +3,13 @@
3
3
  *
4
4
  * Sends through nodemailer (SMTP) or Resend's HTTPS API, and reads,
5
5
  * searches, and fetches the messages it replies to through imapflow (IMAP).
6
+ * A read gives each message's recipients, threading and authentication
7
+ * headers, its text and HTML parts under a bound, and its attachments, whose
8
+ * bytes are fetched only when asked; one message is moved, labelled, flagged
9
+ * or deleted by its UID.
6
10
  */
7
11
  import type { ResendOptions } from './ResendTransport.js';
12
+ import type { EmailAuthentication } from './headers.js';
8
13
  /** The accounts the service uses: an SMTP account or Resend's HTTPS API to send, and IMAP to read. */
9
14
  export interface EmailConfig {
10
15
  /** The SMTP account that sends, and reads when `imap` is not given. Required unless `resend` is given. */
@@ -26,6 +31,15 @@ export interface EmailConfig {
26
31
  port?: number;
27
32
  secure?: boolean;
28
33
  };
34
+ /**
35
+ * The authserv-id of the host that takes mail into the mailbox the service reads, such as `mx.google.com`, compared
36
+ * ignoring case: the `authServId` of a read that names none, and the one the `emailRead` and `emailSearch` tools read
37
+ * under. Without it those tools leave `authentication` out of their answers, since a read that names no authserv-id
38
+ * counts the topmost Authentication-Results field whatever host it names, and on a host that writes no field of its
39
+ * own that field is one the sender wrote. A value that is not text, holds a control character or is white space alone
40
+ * throws an `Error` when the service is made; `null` and the empty string count as not given.
41
+ */
42
+ authServId?: string;
29
43
  }
30
44
  export interface SendEmailOptions {
31
45
  to: string;
@@ -54,6 +68,29 @@ export interface SendEmailOptions {
54
68
  */
55
69
  headers?: Record<string, string>;
56
70
  }
71
+ /** An attachment of a message a read gave: what its body structure says of it, and a read of its bytes. */
72
+ export interface EmailAttachment {
73
+ /** The file name its Content-Disposition, or else its Content-Type, gives; the empty string when neither does. */
74
+ name: string;
75
+ /** Its media type in lowercase, such as `image/png`; `message/rfc822` for a message attached whole. */
76
+ type: string;
77
+ /**
78
+ * Its size as the server holds it, in its transfer encoding (RFC 3501 section 7.4.2), never less than its decoded
79
+ * bytes: about four thirds of them for base64.
80
+ */
81
+ bytes: number;
82
+ /**
83
+ * Fetches the attachment's bytes, decoded, by the message's UID in the folder it was read from, over a connection of
84
+ * its own: a read fetches none of them. Rejects with `EmailMailboxError` `too_large`, having fetched at most the bytes
85
+ * that could decode to the bound, when the decoded bytes pass `maxBytes`, a whole number from 1 to 268,435,456
86
+ * (26,214,400 when not given or `null`), or when its bytes in their transfer encoding pass the most that decode to the
87
+ * bound from an encoder that keeps to its rules (twice it for base64, four times for quoted-printable), as a
88
+ * quoted-printable part written with runs of soft line breaks can; with `gone` when the folder holds no message with
89
+ * that UID, or its UIDVALIDITY changed since the read; with `refused` when the server will not open the folder, as
90
+ * when it was deleted or renamed since the read; and with an `Error` before connecting for any other `maxBytes`.
91
+ */
92
+ read(maxBytes?: number): Promise<Buffer>;
93
+ }
57
94
  /** A message as `readInbox` and `searchEmails` give it. */
58
95
  export interface EmailMessage {
59
96
  /** Its Message-ID field as the server's envelope gives it, or the empty string when it has none. */
@@ -65,13 +102,140 @@ export interface EmailMessage {
65
102
  subject: string;
66
103
  /** Its Date field in ISO 8601, or the empty string when it has none or the field does not read as a date. */
67
104
  date: string;
105
+ /** `text`, or, for a message with no text part, its HTML with each tag written as a space and white space run together. */
68
106
  body: string;
69
- html?: string;
107
+ /**
108
+ * Its HTML parts as written, decoded, joined by a line break in their order, at most `maxBytes` bytes of UTF-8; the
109
+ * empty string when it has none. Of a `multipart/alternative`'s forms only the first that holds HTML counts.
110
+ */
111
+ html: string;
70
112
  flags: string[];
71
113
  uid: number;
114
+ /**
115
+ * The folder's UIDVALIDITY when the message was read, in decimal; the empty string when the server gave none. A UID
116
+ * names the same message only while it holds (RFC 3501 section 2.3.1.1), so a host that keeps the last UID it read
117
+ * keeps this beside it and gives both to its next read.
118
+ */
119
+ uidValidity: string;
120
+ /**
121
+ * The key the mailbox keeps the mail by, the id the server gives where it gives one: the EMAILID (RFC 8474) where the
122
+ * server offers OBJECTID, which imapflow fetches in place of Gmail's id, else Gmail's X-GM-MSGID where it offers
123
+ * X-GM-EXT-1. On any other server, the folder's name, a space and the Message-ID in angle brackets, or, for a message
124
+ * with no Message-ID, the folder's name, a space, its UIDVALIDITY, a colon and its UID. A sender writes the Message-ID,
125
+ * so on such a server a message that copies another's Message-ID has its key: a host that keeps one mail a key compares
126
+ * the UID beside it there. A string to compare and store, not to parse.
127
+ */
128
+ key: string;
129
+ /**
130
+ * Every address its To, Cc and Delivered-To fields name, in lowercase, each once, in that order. A local part that
131
+ * holds white space, a double quote or another special but the dot is given in quotes, as RFC 5322 writes it, such as
132
+ * `"support team"@example.com`, whether the server gave it with its quoting removed or the field kept it.
133
+ */
134
+ recipients: string[];
135
+ /** The first message id its In-Reply-To lists, in angle brackets, or the empty string. */
136
+ inReplyTo: string;
137
+ /** The message ids its References lists, each in angle brackets, in its order. */
138
+ references: string[];
139
+ /** The keyword of its Auto-Submitted field (RFC 3834), in lowercase, such as `auto-replied`; `no` when it has none. */
140
+ autoSubmitted: string;
141
+ /** The keyword of its Precedence field, in lowercase, such as `bulk`; the empty string when it has none. */
142
+ precedence: string;
143
+ /** Whether it carries an X-Autoreply or an X-Auto-Response-Suppress field. */
144
+ autoreply: boolean;
145
+ /** Whether its own type is `multipart/report` (RFC 6522), as a delivery or read report's is. */
146
+ isReport: boolean;
147
+ /**
148
+ * The results of its topmost Authentication-Results field, parsed: the receiving host's, since each host that checks
149
+ * a message adds its field at the top (RFC 8601 section 4), so a field a sender wrote sits below it, and no field
150
+ * below the topmost is read. Given `authServId`, the read's or the service's, the topmost field counts only when it
151
+ * names that host, so a mail whose topmost field names another host, or none, reads `none` throughout. A result the
152
+ * field does not record reads `none`. The topmost field is the receiving host's only when that host writes one on
153
+ * every message it takes in: on a host that writes none, it is one the sender wrote, and `authServId` cannot tell the
154
+ * two apart. A host that writes one field per test, as RFC 8601 section 4 allows, gives its topmost field's results
155
+ * alone.
156
+ */
157
+ authentication: EmailAuthentication;
158
+ /**
159
+ * Its text parts, decoded, joined by a line break in their order, as a picture between two of them leaves them, at
160
+ * most `maxBytes` bytes of UTF-8 cut on a character's edge; the empty string when it has none. Of a
161
+ * `multipart/alternative`'s forms only the first that holds text counts.
162
+ */
163
+ text: string;
164
+ /**
165
+ * Whether its text or its HTML held more than `maxBytes` gives, or a part more bytes in its transfer encoding than the
166
+ * most that decode to the bound from an encoder that keeps to its rules, as a quoted-printable part written with runs
167
+ * of soft line breaks can: a read never fetches more of a part than that.
168
+ */
169
+ truncated: boolean;
170
+ /**
171
+ * Every part that is neither its text nor its HTML, in the message's order: a part marked an attachment or named as a
172
+ * file, a part of any other type, and a message attached whole.
173
+ */
174
+ attachments: EmailAttachment[];
175
+ }
176
+ /** How a read bounds and reads each message, which `readInbox` and `searchEmails` take. */
177
+ export interface ReadOptions {
178
+ /**
179
+ * The most bytes of each message's text and of its HTML given, in UTF-8: a whole number from 1 to 268,435,456, 262,144
180
+ * when not given or `null`. A part is fetched up to the bytes that can decode to what the bound leaves, so a large one
181
+ * is never read whole. Any other value throws an `Error` before any mail is read.
182
+ */
183
+ maxBytes?: number;
184
+ /**
185
+ * The authserv-id of the host that takes mail into the mailbox, such as `mx.google.com`, compared ignoring case: each
186
+ * message's topmost Authentication-Results field counts only when it names that host, and no field below the topmost
187
+ * ever counts, so a field a sender wrote, under that name or another, is read only on a host that writes no field of
188
+ * its own. Without it the service's `authServId` (`EmailConfig.authServId`) counts, and without both the topmost
189
+ * field counts whatever host it names. Give one for any decision that trusts the results. A value that is not text,
190
+ * holds a control character or is white space alone throws an `Error` before any mail is read; `null` and the empty
191
+ * string count as not given.
192
+ */
193
+ authServId?: string;
194
+ }
195
+ /** What `readInbox` reads, given as one object. */
196
+ export interface ReadInboxOptions extends ReadOptions {
197
+ /** The folder read; INBOX when not given or `null`. */
198
+ folder?: string;
199
+ /** The most messages given, a whole number of at least 1; 20 when not given or `null`. */
200
+ limit?: number;
201
+ /**
202
+ * A UID to read after: the first `limit` messages whose UID is greater, whatever their flags, oldest first, selected by
203
+ * `UID SEARCH UID <afterUid + 1>:*`. A whole number from 0, 0 for every message. Without it the read gives the last
204
+ * `limit` messages, newest first. Any other value throws an `Error` before any mail is read.
205
+ */
206
+ afterUid?: number;
207
+ /**
208
+ * The folder's UIDVALIDITY when `afterUid` was read, as each message gives it: a whole number from 1 to 4,294,967,295,
209
+ * or its digits with no leading zero. When the folder's UIDVALIDITY is another, its UIDs were given anew (RFC 3501
210
+ * section 2.3.1.1), so `afterUid` names nothing of it and the read starts from the folder's first message, as
211
+ * `afterUid` 0 does. It counts only with `afterUid`; `null` and the empty string count as not given; any other value
212
+ * throws an `Error` before any mail is read.
213
+ */
214
+ uidValidity?: string | number;
215
+ }
216
+ /** The folder a change to one message acts in. */
217
+ export interface MailboxOptions {
218
+ /** The folder that holds the message; INBOX when not given or `null`. */
219
+ folder?: string;
220
+ }
221
+ /** What made a change to a message, or a read of an attachment, fail. */
222
+ export type EmailMailboxErrorCode = 'gone' | 'refused' | 'unsupported' | 'too_large';
223
+ /**
224
+ * A change to one message, or a read of an attachment, that failed: `gone` when the folder holds no message with the UID
225
+ * named, which a caller that wants the message gone counts as done; `refused` when the server refused the command or
226
+ * would not open the folder; `unsupported` when the server lacks the extension the change needs; `too_large` when an
227
+ * attachment passes the bound a read named. Its message never holds a UID, a folder's name or an address, and it
228
+ * carries nothing the server said.
229
+ */
230
+ export declare class EmailMailboxError extends Error {
231
+ /** The type's own name. */
232
+ name: string;
233
+ /** Why it failed. */
234
+ readonly code: EmailMailboxErrorCode;
235
+ constructor(code: EmailMailboxErrorCode, message: string);
72
236
  }
73
237
  /** What `searchEmails` searches for. */
74
- export interface SearchEmailOptions {
238
+ export interface SearchEmailOptions extends ReadOptions {
75
239
  /** Text the subject, the body or the From holds, ignoring case; every message when empty. */
76
240
  query: string;
77
241
  /** The folder searched; INBOX when not given. */
@@ -94,6 +258,16 @@ export declare class EmailService {
94
258
  private transporter;
95
259
  private resend;
96
260
  private running;
261
+ /**
262
+ * The authserv-id `EmailConfig.authServId` names, in lowercase, or `undefined` when it names none: the `authServId`
263
+ * of a read that names none, and the one the `emailRead` and `emailSearch` tools read under; without it they leave
264
+ * `authentication` out of their answers.
265
+ */
266
+ readonly authServId: string | undefined;
267
+ /**
268
+ * Keeps `config`. An `authServId` that is not text, holds a control character or is white space alone throws an
269
+ * `Error` that holds none of it.
270
+ */
97
271
  constructor(config: EmailConfig);
98
272
  /**
99
273
  * Opens the transport that sends: Resend's API when `resend` is given (`from` is then required), else the SMTP
@@ -136,25 +310,100 @@ export declare class EmailService {
136
310
  messageId: string;
137
311
  }>;
138
312
  /**
139
- * The last `limit` messages of `folder`, newest first: INBOX and 20 when they are not given, and 20 when `limit` is
140
- * `null`. `limit` must be a whole number of at least 1: any other value throws an `Error` before any mail is read,
141
- * where the fetch would name a message past the last one, which a server answers BAD. A message whose Date field does
142
- * not read as a date is given with an empty `date`.
313
+ * Reads `folder`'s messages. Given a folder and a limit, or neither, it gives the last `limit` messages of `folder`,
314
+ * newest first: INBOX and 20 when they are not given, and 20 when `limit` is `null`. Given one object
315
+ * (`ReadInboxOptions`), it reads the same way, or, with `afterUid`, gives the first `limit` messages whose UID is
316
+ * greater, oldest first, whatever their flags: it searches `UID <afterUid + 1>:*` and keeps only the greater UIDs,
317
+ * since a range ending in `*` names the last message even when its UID is not greater (RFC 3501 section 9). Given
318
+ * `uidValidity` with `afterUid`, a folder whose UIDVALIDITY is another is read from its first message, since its UIDs
319
+ * were given anew and `afterUid` names nothing of it.
320
+ *
321
+ * `limit` must be a whole number of at least 1, `afterUid` a whole number from 0, `uidValidity` a UIDVALIDITY,
322
+ * `maxBytes` a whole number from 1 to 268,435,456, `folder` a name with no control character and `authServId` text
323
+ * with no control character that is not white space alone: any other value throws an `Error` before any mail is
324
+ * read, where the fetch would name a message past the last one, which a server answers BAD. A read that names no
325
+ * `authServId` reads under the service's. A server that refuses the search after a UID rejects the read with
326
+ * `EmailMailboxError` `refused`. A message whose Date field does not read as a date is given with an empty `date`.
327
+ *
328
+ * Each message is fetched with its envelope, flags, body structure and the header fields its recipients, threading
329
+ * and authentication come from, then its text and HTML parts by UID, each up to the bytes that can decode to what
330
+ * `maxBytes` leaves, in one fetch for the messages whose parts share their sections and windows. No fetch marks a
331
+ * message seen, and no attachment's bytes are fetched until its `read()` is called. A FETCH response the server sends
332
+ * unasked during a fetch, such as the flags another session changed, is no message of the read.
143
333
  */
144
- readInbox(folder?: string, limit?: number): Promise<EmailMessage[]>;
334
+ readInbox(folder?: string | ReadInboxOptions, limit?: number): Promise<EmailMessage[]>;
145
335
  /**
146
336
  * The messages of `folder` (INBOX unless given) whose subject, body or From holds `query`, ignoring case, received on
147
337
  * or after `since` when it is given: the last `limit` (20 unless given) the search finds, newest first. The search
148
- * answers UIDs, and the messages are fetched by those UIDs, which need not follow the messages' sequence numbers.
338
+ * answers UIDs, and the messages are fetched by those UIDs, which need not follow the messages' sequence numbers. Each
339
+ * message carries what `readInbox` gives, its parts bounded by `maxBytes` and its Authentication-Results read as
340
+ * `authServId` says.
149
341
  *
150
342
  * `limit` must be a whole number of at least 1, and `since` an ISO 8601 date, such as `2026-10-15`, alone or followed
151
343
  * by a time and an offset, which are checked and then disregarded: the search starts from the day the date names, as
152
344
  * written, on every server. Either counts as not given when `null`, and `since` when empty. Any other value of either,
153
345
  * a day its month lacks such as `2026-02-30` among them, throws an `Error` before the search: imapflow leaves a date it
154
346
  * cannot read out of the search, which would answer mail of every date, and `Date` rolls a day past its month's end
155
- * over into the next month. A message whose Date field does not read as a date is given with an empty `date`.
347
+ * over into the next month. `maxBytes` and `authServId` are checked as `readInbox` checks them. A message whose Date
348
+ * field does not read as a date is given with an empty `date`.
156
349
  */
157
350
  searchEmails(options: SearchEmailOptions): Promise<EmailMessage[]>;
351
+ /**
352
+ * Moves the message whose UID is `uid` in `options.folder` (INBOX unless given) to `destination`: by `UID MOVE`
353
+ * (RFC 6851) where the server offers MOVE, else by `UID COPY`, the `\Deleted` flag and `UID EXPUNGE` (RFC 4315), which
354
+ * expunges no other message marked deleted. The copy must succeed before the original is touched.
355
+ *
356
+ * Rejects with `EmailMailboxError`: `gone` when the folder holds no message with that UID, which comes before any other
357
+ * answer, and when another session expunges the message before the move, as the COPYUID a server with UIDPLUS sends
358
+ * tells, or as a search after a copy that named none tells; `unsupported`, before anything changes, when the server
359
+ * offers neither MOVE nor UIDPLUS, where `EXPUNGE` would remove every message marked deleted; `refused` when the server
360
+ * refuses a command, as it does a destination that does not exist, or will not open the folder. A move another
361
+ * session's expunge overtakes on a server that sends no COPYUID resolves as done, the server having answered OK. A
362
+ * `uid` that is not a whole number from 1 to 4,294,967,295, or a folder or destination that is not a name, throws an
363
+ * `Error` before connecting.
364
+ */
365
+ moveTo(uid: number, destination: string, options?: MailboxOptions | null): Promise<void>;
366
+ /**
367
+ * Labels the message whose UID is `uid` in `options.folder` (INBOX unless given) with `label`: through Gmail's
368
+ * `X-GM-LABELS` where the server offers X-GM-EXT-1, else by `UID COPY` into the folder named `label`. The message
369
+ * stays where it is.
370
+ *
371
+ * Rejects with `EmailMailboxError` `gone` when the folder holds no message with that UID, and when another session
372
+ * expunges the message between the search for it and the change, which then did nothing (RFC 3501 section 6.4.8), as
373
+ * the COPYUID a server with UIDPLUS sends, or a search after the change, tells; and `refused` when the server refuses
374
+ * the command, as it does a copy into a folder that does not exist, or will not open the folder. A `uid` that is not
375
+ * a whole number from 1 to 4,294,967,295, or a folder or label that is not a name, throws an `Error` before
376
+ * connecting.
377
+ */
378
+ addLabel(uid: number, label: string, options?: MailboxOptions | null): Promise<void>;
379
+ /**
380
+ * Replaces the flags of the message whose UID is `uid` in `options.folder` (INBOX unless given) with `flags`, by
381
+ * `UID STORE ... FLAGS`; an empty list clears them. Each flag is a flag name such as `\Seen` or `$Handled`; `\Recent`,
382
+ * which the server alone sets, is refused. A keyword goes in the spelling the folder's PERMANENTFLAGS give it, matched
383
+ * ignoring case, as IMAP reads a flag's name.
384
+ *
385
+ * Rejects with `EmailMailboxError` `gone` when the folder holds no message with that UID, which comes before any other
386
+ * answer, and when another session expunges the message between the search for it and the STORE, as a search after the
387
+ * STORE tells; `refused`, before anything is stored, when the folder's PERMANENTFLAGS keep neither `\*` nor every flag
388
+ * named, in any spelling, since imapflow would leave such a flag out and a set left with none clears every flag; and
389
+ * `refused` when the server refuses the STORE or will not open the folder. A `uid` that is not a whole number from 1
390
+ * to 4,294,967,295, flags that are not a list of flag names, or a folder that is not a name, throw an `Error` before
391
+ * connecting.
392
+ */
393
+ setFlags(uid: number, flags: string[], options?: MailboxOptions | null): Promise<void>;
394
+ /**
395
+ * Deletes the message whose UID is `uid` in `options.folder` (INBOX unless given): the `\Deleted` flag, then
396
+ * `UID EXPUNGE` of that UID alone (RFC 4315), so no other message marked deleted is expunged. What an expunge does with
397
+ * the message beyond the folder is the server's to decide.
398
+ *
399
+ * Rejects with `EmailMailboxError`: `gone` when the folder holds no message with that UID, which a caller that wants
400
+ * the message gone counts as done, so a second call rejects with it, and which comes before any other answer;
401
+ * `unsupported`, before anything changes, when the server does not offer UIDPLUS, where `EXPUNGE` would remove every
402
+ * message marked deleted; `refused` when the server refuses the STORE or the expunge, or will not open the folder. A
403
+ * message another session expunges during the delete is gone either way, and the delete resolves. A `uid` that is not
404
+ * a whole number from 1 to 4,294,967,295, or a folder that is not a name, throws an `Error` before connecting.
405
+ */
406
+ deleteMessage(uid: number, options?: MailboxOptions | null): Promise<void>;
158
407
  /**
159
408
  * The codes in the INBOX message whose Message-ID is `messageId`, given with or without its angle brackets: the
160
409
  * matches of `pattern` when it is given, else runs of four to eight digits, then the codes that phrases such as "code
@@ -197,6 +446,34 @@ export declare class EmailService {
197
446
  messageId: string;
198
447
  }>;
199
448
  private getImapClient;
449
+ /**
450
+ * The messages a set names in the selected folder, as a read gives them: first their envelopes, flags, body
451
+ * structures and read header fields, then their text and HTML parts by UID under the bound.
452
+ */
453
+ private fetchMessages;
454
+ /**
455
+ * The text and HTML bytes of each message by UID, each part up to one byte past its window: one fetch for the messages
456
+ * whose parts share their sections and windows, and no fetch of a part whose window is 0, since the parts before it
457
+ * fill the bound.
458
+ */
459
+ private fetchTextParts;
460
+ /**
461
+ * An attachment's bytes, decoded, fetched by the message's UID in `folder` over a connection of its own, up to one byte
462
+ * past the bytes that can decode to the bound: an attachment's `read()`.
463
+ */
464
+ private readAttachment;
465
+ /**
466
+ * Runs `work` on the message whose UID is `uid` in `folder`, over a connection of its own, once a search finds the UID:
467
+ * a UID STORE, COPY or MOVE of a UID no message has succeeds having done nothing (RFC 3501 section 6.4.8, which RFC 6851
468
+ * section 3.2 extends to UID MOVE), so no message with it rejects with `gone`, before `work` checks what the server
469
+ * offers, since a caller that wants the message gone counts gone as done.
470
+ */
471
+ private onMessage;
472
+ /**
473
+ * Marks one message deleted and expunges it by UID, on a server the caller found to offer UIDPLUS. imapflow's delete
474
+ * goes on to the expunge whatever its STORE answered, so the STORE is sent and checked first.
475
+ */
476
+ private expungeOne;
200
477
  private extractPlainText;
201
478
  private extractHtmlBody;
202
479
  private decodeQuotedPrintable;
@@ -1 +1 @@
1
- {"version":3,"file":"EmailService.d.ts","sourceRoot":"","sources":["../src/EmailService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EmailService.d.ts","sourceRoot":"","sources":["../src/EmailService.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAK1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAQxD,sGAAsG;AACtG,MAAM,WAAW,WAAW;IAC1B,0GAA0G;IAC1G,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,wFAAwF;IACxF,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,iIAAiI;IACjI,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,2GAA2G;AAC3G,MAAM,WAAW,eAAe;IAC9B,kHAAkH;IAClH,IAAI,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1C;AAED,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAC3B,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,6GAA6G;IAC7G,IAAI,EAAE,MAAM,CAAC;IACb,2HAA2H;IAC3H,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,uHAAuH;IACvH,aAAa,EAAE,MAAM,CAAC;IACtB,4GAA4G;IAC5G,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,gGAAgG;IAChG,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;;;;;OASG;IACH,cAAc,EAAE,mBAAmB,CAAC;IACpC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC;AAED,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mDAAmD;AACnD,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;AAED,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,yEAAyE;AACzE,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAErF;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,2BAA2B;IAClB,IAAI,SAAuB;IAEpC,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;gBAEzB,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM;CAIzD;AAED,wCAAwC;AACxC,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,6FAA6F;IAC7F,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqSD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,OAAO,CAAS;IAExB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAExC;;;OAGG;gBACS,MAAM,EAAE,WAAW;IAK/B;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAS/B,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGxC;IAID;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAiD1E;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAmD5F;;;;;;;;;;;;;;OAcG;IACG,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAiDxE;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B9F;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB1F;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAa5F;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAYhF;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IA0DxG;;;;;;;;;;;;;;;;;;;OAmBG;IACG,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;YAoEnB,aAAa;IAuB3B;;;OAGG;YACW,aAAa;IA6B3B;;;;OAIG;YACW,cAAc;IA0B5B;;;OAGG;YACW,cAAc;IAqC5B;;;;;OAKG;YACW,SAAS;IAevB;;;OAGG;YACW,UAAU;IASxB,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,cAAc;CAGvB"}