Skip to content

Commit 5e075ec

Browse files
committed
docs(mail): name maxCommandBytes in the changeset signature, the announcement charge, and the listing ceiling
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ
1 parent 2725aac commit 5e075ec

3 files changed

Lines changed: 3 additions & 2 deletions

File tree

‎.changeset/22871-imap-mailbox-sync.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Inbound mail has a mail source: the new `@objectstack/connector-imap` reads a ma
88
Clause-②: yes (a new published package, `@objectstack/connector-imap`, and its exports; new exports and plugin options in `@objectstack/service-mailbox`, and four new fields on its published `sys_mailbox` object)
99

1010
- **`@objectstack/connector-imap` (new) — protocol and authentication only.**
11-
- `openImapSession({ account, credentials, timeoutMs?, maxLiteralBytes? })` opens a read-only IMAP4rev1 session: `examine(folder)` (`EXAMINE`, never `SELECT`; answers `uidValidity`, `uidNext`, `exists`), `listMessages(fromUid)`, `fetchMessages(uids)` (`BODY.PEEK[]`, so no message is marked `\Seen`), `logout()` and `close()`. A folder opened without a UIDVALIDITY is refused.
11+
- `openImapSession({ account, credentials, timeoutMs?, maxLiteralBytes?, maxCommandBytes? })` opens a read-only IMAP4rev1 session: `examine(folder)` (`EXAMINE`, never `SELECT`; answers `uidValidity`, `uidNext`, `exists`), `listMessages(fromUid)`, `fetchMessages(uids)` (`BODY.PEEK[]`, so no message is marked `\Seen`), `logout()` and `close()`. A folder opened without a UIDVALIDITY is refused.
1212
- The account and credential abstraction is shaped for the per-user mailbox: a `MailAccount` (`{ id, username, imap: { host, port?, security? } }`) and a `MailCredentialProvider` that resolves the account's credential each time a session opens. `staticCredentials(credential)` answers one fixed credential.
1313
- A `password` credential is sent with `AUTHENTICATE PLAIN` when the server offers it, otherwise `LOGIN`; an `oauth2` access token with `AUTHENTICATE XOAUTH2`. A server that refuses `LOGIN` and offers no `PLAIN` is refused before anything is sent.
1414
- `security: 'tls'` (the default) is implicit TLS with the certificate verified. A plaintext connection (`security: 'none'`) is refused to any host but loopback, before a socket opens.

‎packages/connectors/connector-imap/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ It registers no `connector_action` handlers and ships no plugin: nothing in a fl
1414
- **`MailAccount`** — an account is an identity with its own endpoint and login (`{ id, username, imap: { host, port?, security? } }`), never a process-wide setting, so many accounts coexist.
1515
- **`MailCredentialProvider`** — the credential is resolved per account, each time a session opens, and never stored on the account or cached by the connector. That is what lets a per-user mailbox later read a user's own stored credential and refresh an OAuth token without the connector changing. `staticCredentials(credential)` answers one fixed credential — the shared mailbox whose host supplies it at composition.
1616
- **`ImapError`** — every failure, with a `kind`: `configuration`, `connection`, `timeout`, `authentication`, `refused`, `protocol` or `oversized`. A message never carries the credential.
17-
- **Bounds against an untrusted server** — a line above 4 MiB, a literal above `maxLiteralBytes` (64 MiB by default), a response of more than 32 literals and more than `maxCommandBytes` of responses to one command (128 MiB by default, each response charged 256 bytes beyond its own) are refused as `oversized` before the excess is kept, and the connection is closed. The silence bound (`timeoutMs`) is an inactivity bound; these are what stop a server that trickles forever.
17+
- **Bounds against an untrusted server** — a line above 4 MiB, a literal above `maxLiteralBytes` (64 MiB by default), a response of more than 32 literals and more than `maxCommandBytes` of responses to one command (128 MiB by default, each response charged 256 bytes beyond its own, and the response being read charged at each literal's announcement, before its octets are read) are refused as `oversized` before the excess is kept, and the connection is closed. The silence bound (`timeoutMs`) is an inactivity bound; these are what stop a server that trickles forever.
1818

1919
## Authentication
2020

‎packages/services/service-mailbox/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Inbound mail for ObjectStack: the mirrored message, its links to an app's record
3232
- **the cursor** is kept on the mailbox row — `imap_folder`, `imap_uid_validity`, `imap_last_uid`, `imap_synced_at` — and written by the sync only. A cursor taken under another UIDVALIDITY, or in another folder, is discarded and the folder is read from its start; every message already stored then answers `duplicate`;
3333
- the cursor moves after each batch, past every message stored, already stored, suppressed or deliberately skipped. A store failure stops the run below the failed message, so the next run retries it. A message above `maxMessageBytes` (32 MiB by default) or one the seam refuses as unreadable is skipped and logged at `error`, once, with its UID — it stays in the IMAP folder;
3434
- runs are bounded (`maxMessagesPerRun`, 500 by default), and each fetch by count and by listed bytes (`batchSize`, 25, and `maxBatchBytes`, 64 MiB). The session refuses any literal above `maxMessageBytes` plus a 64 KiB framing envelope, so a server that omits or understates a message's size cannot hand over more: the batch is read again one message at a time on a fresh session, that message is skipped as `too_large` and logged, and the rest keeps arriving. One mailbox is never synced twice at once in one process, and two processes syncing it at once store nothing twice;
35+
- the listing of the UIDs above the cursor is one IMAP command, kept under the session's command bound (the connector's `maxCommandBytes`, which the sync sets to the larger of 128 MiB and `maxBatchBytes` + `maxMessageBytes` + 64 KiB) at about 300 bytes per message listed, so at the defaults a folder with more than about 450,000 messages above the cursor is refused `oversized` on the listing, on every poll, until the host raises that bound through `maxBatchBytes` (paging the listing is left to the per-user phase);
3536
- a run in which a mailbox could not be synced (an unreachable server, a refused credential) reports `degraded` to the job service, and logs the mailbox and the reason. Without a job service the boot says so, and `plugin.syncService.syncAll({ isSystem: true })` runs a pass on your own schedule.
3637
- **No credential is stored by this package.** The account and its credential come from your `accountFor`, asked per mailbox per run, so they live wherever your host keeps secrets (the environment, a vault) — the way every open connector takes its credential.
3738

0 commit comments

Comments
 (0)