chore(publish): per-package READMEs and MIT license field for all 9 packages#76
Merged
chore(publish): per-package READMEs and MIT license field for all 9 packages#76
Conversation
6 tasks
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
The JSDoc above `AuthAdapter` said "App passwords are stored hashed (argon2) in D1 and verified here." That's wrong on two axes: 1. The IMAP server does not own credential storage or hashing. That was the original design but it was rewritten: AuthAdapter is an interface contract only, the consumer brings their own auth system. Prescribing argon2 contradicts the rewritten contract. 2. Core docs must be generic: "blob storage" not "R2", "database" not "D1". Referencing D1 by name inside the core protocol package violates the zero-vendor-dependency principle. Replaced with a proper JSDoc block that: - States the interface-only posture explicitly - Names the consumer's ownership (storage, hashing, generation, revocation) - Lists the security guarantees the IMAP server DOES enforce regardless of adapter implementation (generic failure response, rate limit, disconnect after max attempts) - Refers to the adapter lifecycle in neutral language This matches the authoritative `authentication.md` doc shipped in PR #76 and the interface-only posture in the `@rafters/mail-imap-server` quickstart.md fixed in PR #78. Verification: - pnpm --filter @rafters/mail-imap test: 313 passing - pnpm --filter @rafters/mail-imap typecheck: clean - pnpm --filter @rafters/mail-imap build: ESM + dts clean - pnpm lint: 0 warnings, 0 errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
After shipping the docs in PR #76 I spot-read them and found real interface drift -- types, method signatures, and import paths that did not match the current source. These would have broken every consumer that copy-pasted the examples. The @vault-2026 technical accuracy review covered the schema sections of core-reference.md but did not cover the adapter package docs, which is where these errors sit. ## packages/resend/docs/outbound.md - EmailProvider.sendEmail: type names wrong. Doc said `sendEmail(params: SendEmailParams): Promise<SendEmailResult>`. Actual is `sendEmail(params: EmailParams): Promise<{ id: string }>`. - EmailParams field table wrong: `from` is optional not required, `to` is a single string not array, `cc`/`bcc`/`headers` do not exist in EmailParams. - Documented the full EmailProvider surface (mailing lists, subscribers, campaigns, audiences), which was omitted. - Method calls wrong: doc called `mailService.compose(...)` and `mailService.replyToThread(...)`. Actual methods are `InboxEmailService.composeEmail` and `replyToThread`. - Thread metadata update: doc said `messageCount`, schema has `unreadCount`. ## packages/cloudflare/docs/blob-storage.md - BlobStorage interface signatures wrong: missing `options?` on `put` and `get`, `get` return type was `string | undefined` (actual is `BlobObject | null`), and `generateKey` was missing entirely. - Added BlobObject, BlobPutOptions, and BlobGetOptions types alongside so consumers see the full shape. ## packages/cloudflare/docs/inbound.md - InboundAdapter.handleIncoming return type wrong: said `Promise<void>`, actual is `Promise<{ messageId, threadId }>`. - "Inbound email structure" section described a 14-field parsed form as if it were the input type. The actual InboundEmail is 4 fields: `raw: ArrayBuffer`, `from`, `to`, `headers`. The parsed fields land on the message row AFTER the adapter does parsing work. Rewrote with a clear split between input shape and parsed-form schema fields. - Classification section: `ClassificationAdapter` should be `EmailClassifier`, `summary` field does not exist on `EmailClassification` (actual fields are `tags`, `priority`). ## packages/workers-ai/docs/classification.md - Interface name wrong: `ClassificationAdapter` should be `EmailClassifier`. - Method signature wrong: `classify(message: ClassificationInput)` should be `classify(from: string, subject: string, body: string)`. - Result type wrong: `ClassificationResult` with `summary` field should be `EmailClassification` with `tags` and `priority` fields. - Example classifier implementation updated to the correct shape. ## packages/cloudflare/docs/quickstart.md - Worker example imported `createR2BlobStorage` and `parseInboundEmail` from the package root. Actual exports are `createR2Storage` (from `/storage` subpath) and `parseEmailHeaders` / `hashContent` (from `/parsing` subpath). Also `createR2Storage` takes `{ bucket }` config, not a raw R2Bucket. - `blobStorage.put(parsed.rawEmail)` was missing the required `key` parameter. Rewrote to use `storage.generateKey` and `storage.put(key, raw)`. - `sendEmail` example showed `to: [array]`, actual is a single string. - "Next steps" link list pointed at 5 docs at `./classification.md`, `./imap-quickstart.md`, etc. Only `./classification.md` does not exist in this package -- the rest live in other packages. Replaced with per-package npm links. ## Verification - pnpm test: 609 passing across 37 files (no code change, same count as before) - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - oxfmt normalized the 5 modified docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tasks
…ackages Adds a README.md in every package so the npm package page renders properly instead of "no readme available". Adds "license": "MIT" to every package.json so the license badge links via npm's SPDX lookup to the MIT text (the actual LICENSE file lives in the org .github repo, not this repo). READMEs cover per-package scope, install command, minimal usage examples, subpath export table where applicable, and a link back to the monorepo README for the wider architecture. Each one starts with a single-paragraph identity statement so the npm consumer sees what the package is for within three seconds of landing. Files added (9): - packages/core/README.md - packages/imap/README.md - packages/imap-cloudflare/README.md - packages/imap-server/README.md - packages/resend/README.md - packages/cloudflare/README.md - packages/react-email/README.md - packages/workers-ai/README.md - packages/better-auth-resend/README.md Files modified (9): - all 9 package.json files -- add "license": "MIT" after description Verification: - pnpm publish --dry-run on all 9 packages shows README.md in the tarball contents and the correct file count bump (+1 per package). CHANGELOGs are not hand-written -- they are auto-generated by the pnpm changeset version step from the pending initial-release.md changeset during the release flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every package now has its own docs directory with topic-scoped
documentation, shipped in the npm tarball via files: ["dist", "docs"].
Consumers landing on an npm package page see real documentation, not
just a one-line README.
## Per-package docs
- **core** (5 docs):
- reference.md (51.5 kB) -- complete schema and API reference for
all 13 tables, service interfaces, Zod schemas, design decisions
- threading.md -- RFC 5322 threading via In-Reply-To / References
- migrations.md -- database migration workflow
- newsletters.md -- mailing lists, subscribers, campaigns
- adapters.md -- how adapters connect core to external services
- **imap** (2 docs):
- commands.md -- full IMAP4rev1 command reference with RFC section
citations
- authentication.md -- AuthAdapter contract and consumer ownership
- **imap-server** (2 docs):
- quickstart.md -- zero-to-running IMAP server
- deployment.md -- Fly, Railway, Fargate, Docker, VPS per-platform
guides with TLS handling
- **imap-cloudflare** (1 doc):
- deployment.md -- Durable Object setup, bindings, cost model
- **resend** (1 doc):
- outbound.md -- compose, send, webhook handling, audit
- **cloudflare** (3 docs):
- quickstart.md -- Workers + D1 + R2 + Email Routing from zero
- inbound.md -- parsing, dedupe, blob storage, thread matching
- blob-storage.md -- R2 adapter, key schema, retrieval
- **react-email** (1 doc, newly written):
- templates.md -- BaseEmail, OtpEmail, custom templates, renderer
- **workers-ai** (1 doc):
- classification.md -- categories, priority derivation, tag patterns
- **better-auth-resend** (1 doc, newly written):
- usage.md -- full better-auth integration, configuration, and how
to replace the default template
Plus repo-level overview docs that do not ship with any package:
- docs/architecture.md -- 580 lines of internal design reference
- docs/getting-started.md -- 397 lines of cross-package quickstart
## Source
Most docs were copied from vault-2026/projects/rafters/mail-docs/
(the 16 docs written during the IMAP phase but never integrated into
the package tree). The two new ones (react-email/docs/templates.md,
better-auth-resend/docs/usage.md) were written fresh because vault
had nothing allocated to those packages.
## Shipping
Every package.json files field updated to ["dist", "docs"]. Every
README gained a Documentation section listing the local docs with
descriptions.
Verified via pnpm publish --dry-run on all 9 packages: each tarball
now includes the docs/ tree and the total file count reflects the
additions.
| Package | Docs files | Tarball size | Total files |
|-----------------------|-----------:|-------------:|------------:|
| @rafters/mail | 5 | 47.6 kB | 34 |
| @rafters/mail-imap | 2 | 24.9 kB | 36 |
| @rafters/mail-imap-server | 2 | 8.0 kB | 6 |
| @rafters/mail-imap-cloudflare | 1 | 7.4 kB | 11 |
| @rafters/mail-resend | 1 | 8.5 kB | 13 |
| @rafters/mail-cloudflare | 3 | 7.6 kB | 13 |
| @rafters/mail-react-email | 1 | 5.0 kB | 14 |
| @rafters/mail-workers-ai | 1 | 5.4 kB | 8 |
| @rafters/better-auth-resend | 1 | 3.1 kB | 5 |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-CI format check caught 26 docs and READMEs that were not normalized to oxfmt's canonical markdown form. CI runs pnpm format:check after build / typecheck / lint, so unnormalized markdown fails the gate even when the content is correct. Running pnpm format in place resolved every file in one pass. No content changes. Whitespace, table alignment, and code block formatting only. Affected files (26): - docs/architecture.md, docs/getting-started.md (repo-level) - packages/*/README.md (9 per-package READMEs) - packages/*/docs/*.md (15 per-package docs) Verification: - pnpm format:check: clean - pnpm test: 609 passing - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
d24e180 to
ada95a9
Compare
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
After shipping the docs in PR #76 I spot-read them and found real interface drift -- types, method signatures, and import paths that did not match the current source. These would have broken every consumer that copy-pasted the examples. The @vault-2026 technical accuracy review covered the schema sections of core-reference.md but did not cover the adapter package docs, which is where these errors sit. - EmailProvider.sendEmail: type names wrong. Doc said `sendEmail(params: SendEmailParams): Promise<SendEmailResult>`. Actual is `sendEmail(params: EmailParams): Promise<{ id: string }>`. - EmailParams field table wrong: `from` is optional not required, `to` is a single string not array, `cc`/`bcc`/`headers` do not exist in EmailParams. - Documented the full EmailProvider surface (mailing lists, subscribers, campaigns, audiences), which was omitted. - Method calls wrong: doc called `mailService.compose(...)` and `mailService.replyToThread(...)`. Actual methods are `InboxEmailService.composeEmail` and `replyToThread`. - Thread metadata update: doc said `messageCount`, schema has `unreadCount`. - BlobStorage interface signatures wrong: missing `options?` on `put` and `get`, `get` return type was `string | undefined` (actual is `BlobObject | null`), and `generateKey` was missing entirely. - Added BlobObject, BlobPutOptions, and BlobGetOptions types alongside so consumers see the full shape. - InboundAdapter.handleIncoming return type wrong: said `Promise<void>`, actual is `Promise<{ messageId, threadId }>`. - "Inbound email structure" section described a 14-field parsed form as if it were the input type. The actual InboundEmail is 4 fields: `raw: ArrayBuffer`, `from`, `to`, `headers`. The parsed fields land on the message row AFTER the adapter does parsing work. Rewrote with a clear split between input shape and parsed-form schema fields. - Classification section: `ClassificationAdapter` should be `EmailClassifier`, `summary` field does not exist on `EmailClassification` (actual fields are `tags`, `priority`). - Interface name wrong: `ClassificationAdapter` should be `EmailClassifier`. - Method signature wrong: `classify(message: ClassificationInput)` should be `classify(from: string, subject: string, body: string)`. - Result type wrong: `ClassificationResult` with `summary` field should be `EmailClassification` with `tags` and `priority` fields. - Example classifier implementation updated to the correct shape. - Worker example imported `createR2BlobStorage` and `parseInboundEmail` from the package root. Actual exports are `createR2Storage` (from `/storage` subpath) and `parseEmailHeaders` / `hashContent` (from `/parsing` subpath). Also `createR2Storage` takes `{ bucket }` config, not a raw R2Bucket. - `blobStorage.put(parsed.rawEmail)` was missing the required `key` parameter. Rewrote to use `storage.generateKey` and `storage.put(key, raw)`. - `sendEmail` example showed `to: [array]`, actual is a single string. - "Next steps" link list pointed at 5 docs at `./classification.md`, `./imap-quickstart.md`, etc. Only `./classification.md` does not exist in this package -- the rest live in other packages. Replaced with per-package npm links. - pnpm test: 609 passing across 37 files (no code change, same count as before) - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - oxfmt normalized the 5 modified docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
The JSDoc above `AuthAdapter` said "App passwords are stored hashed (argon2) in D1 and verified here." That's wrong on two axes: 1. The IMAP server does not own credential storage or hashing. That was the original design but it was rewritten: AuthAdapter is an interface contract only, the consumer brings their own auth system. Prescribing argon2 contradicts the rewritten contract. 2. Core docs must be generic: "blob storage" not "R2", "database" not "D1". Referencing D1 by name inside the core protocol package violates the zero-vendor-dependency principle. Replaced with a proper JSDoc block that: - States the interface-only posture explicitly - Names the consumer's ownership (storage, hashing, generation, revocation) - Lists the security guarantees the IMAP server DOES enforce regardless of adapter implementation (generic failure response, rate limit, disconnect after max attempts) - Refers to the adapter lifecycle in neutral language This matches the authoritative `authentication.md` doc shipped in PR #76 and the interface-only posture in the `@rafters/mail-imap-server` quickstart.md fixed in PR #78. Verification: - pnpm --filter @rafters/mail-imap test: 313 passing - pnpm --filter @rafters/mail-imap typecheck: clean - pnpm --filter @rafters/mail-imap build: ESM + dts clean - pnpm lint: 0 warnings, 0 errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
The JSDoc above `AuthAdapter` said "App passwords are stored hashed (argon2) in D1 and verified here." That's wrong on two axes: 1. The IMAP server does not own credential storage or hashing. That was the original design but it was rewritten: AuthAdapter is an interface contract only, the consumer brings their own auth system. Prescribing argon2 contradicts the rewritten contract. 2. Core docs must be generic: "blob storage" not "R2", "database" not "D1". Referencing D1 by name inside the core protocol package violates the zero-vendor-dependency principle. Replaced with a proper JSDoc block that: - States the interface-only posture explicitly - Names the consumer's ownership (storage, hashing, generation, revocation) - Lists the security guarantees the IMAP server DOES enforce regardless of adapter implementation (generic failure response, rate limit, disconnect after max attempts) - Refers to the adapter lifecycle in neutral language This matches the authoritative `authentication.md` doc shipped in PR #76 and the interface-only posture in the `@rafters/mail-imap-server` quickstart.md fixed in PR #78. Verification: - pnpm --filter @rafters/mail-imap test: 313 passing - pnpm --filter @rafters/mail-imap typecheck: clean - pnpm --filter @rafters/mail-imap build: ESM + dts clean - pnpm lint: 0 warnings, 0 errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
After shipping the docs in PR #76 I spot-read them and found real interface drift -- types, method signatures, and import paths that did not match the current source. These would have broken every consumer that copy-pasted the examples. The @vault-2026 technical accuracy review covered the schema sections of core-reference.md but did not cover the adapter package docs, which is where these errors sit. - EmailProvider.sendEmail: type names wrong. Doc said `sendEmail(params: SendEmailParams): Promise<SendEmailResult>`. Actual is `sendEmail(params: EmailParams): Promise<{ id: string }>`. - EmailParams field table wrong: `from` is optional not required, `to` is a single string not array, `cc`/`bcc`/`headers` do not exist in EmailParams. - Documented the full EmailProvider surface (mailing lists, subscribers, campaigns, audiences), which was omitted. - Method calls wrong: doc called `mailService.compose(...)` and `mailService.replyToThread(...)`. Actual methods are `InboxEmailService.composeEmail` and `replyToThread`. - Thread metadata update: doc said `messageCount`, schema has `unreadCount`. - BlobStorage interface signatures wrong: missing `options?` on `put` and `get`, `get` return type was `string | undefined` (actual is `BlobObject | null`), and `generateKey` was missing entirely. - Added BlobObject, BlobPutOptions, and BlobGetOptions types alongside so consumers see the full shape. - InboundAdapter.handleIncoming return type wrong: said `Promise<void>`, actual is `Promise<{ messageId, threadId }>`. - "Inbound email structure" section described a 14-field parsed form as if it were the input type. The actual InboundEmail is 4 fields: `raw: ArrayBuffer`, `from`, `to`, `headers`. The parsed fields land on the message row AFTER the adapter does parsing work. Rewrote with a clear split between input shape and parsed-form schema fields. - Classification section: `ClassificationAdapter` should be `EmailClassifier`, `summary` field does not exist on `EmailClassification` (actual fields are `tags`, `priority`). - Interface name wrong: `ClassificationAdapter` should be `EmailClassifier`. - Method signature wrong: `classify(message: ClassificationInput)` should be `classify(from: string, subject: string, body: string)`. - Result type wrong: `ClassificationResult` with `summary` field should be `EmailClassification` with `tags` and `priority` fields. - Example classifier implementation updated to the correct shape. - Worker example imported `createR2BlobStorage` and `parseInboundEmail` from the package root. Actual exports are `createR2Storage` (from `/storage` subpath) and `parseEmailHeaders` / `hashContent` (from `/parsing` subpath). Also `createR2Storage` takes `{ bucket }` config, not a raw R2Bucket. - `blobStorage.put(parsed.rawEmail)` was missing the required `key` parameter. Rewrote to use `storage.generateKey` and `storage.put(key, raw)`. - `sendEmail` example showed `to: [array]`, actual is a single string. - "Next steps" link list pointed at 5 docs at `./classification.md`, `./imap-quickstart.md`, etc. Only `./classification.md` does not exist in this package -- the rest live in other packages. Replaced with per-package npm links. - pnpm test: 609 passing across 37 files (no code change, same count as before) - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - oxfmt normalized the 5 modified docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
… react-email Second pass of accuracy fixes after finding more drift while reviewing the 967-line adapters.md and cross-referencing against actual source. Some of these errors were also present in files I introduced in PR #76 (now on main) -- fixing those here too. ## packages/core/docs/adapters.md **Resend section:** - Replaced `new ResendService(...) + new ResendProvider(resend)` with the actual factory: `createResendProvider({ apiKey, fromEmail })`. `ResendProvider` is not an exported class; `createResendProvider` is a factory function. - `CampaignParams.audienceId` -> `listId`. The field name in the actual `campaignParamsSchema` is `listId`. - Replaced `new MockEmailProvider()` with `createMockEmailProvider()`. Mock is also a factory, not a class. - Documented the real mock surface: `sentEmails` as a top-level property, `getState()` for full internal state introspection, `clear()` for test resets. **Cloudflare section:** - Removed all references to `handleInboundEmail` (does not exist) and `R2BlobStorage` class (does not exist). Replaced with the actual building blocks: `createR2Storage`, `parseEmailHeaders`, `hashContent`. - Fixed the `BlobStorage` interface to match the real shape: single `put(key, content, options?)`, `get(key, options?)` returns `BlobObject | null`, `delete(key)`, `generateKey(contentHash, extension)`. The doc had `putRaw`/`putText`/`putHtml` three-method split, which is not how the interface is shaped. - Worker entry point example rewritten to use the actual exports and demonstrate the compose-your-own pattern (read bytes, parse headers, hash, store, insert row). - Thread matching: clarified it is the consumer's responsibility, not a feature of the adapter. The adapter provides the parsing building blocks; thread matching queries are the consumer's code. - `InMemoryBlobStorage` example rewritten as a factory function returning `BlobStorage` with the correct four methods. **Workers AI section:** - Removed `ClassifyEmailWorkflow` and `handleEmailClassifyQueue` references (do not exist). The package does not ship a workflow subpath or a queue subpath. - Replaced `createEmailClassifier` with the real factory name `createWorkersAIClassifier(ai, config?)`. - Queue consumer example rewritten to show consumer-implemented queue handling using the real classifier. - Mock AI type is now `AiBinding` from the package, not `Ai`. **React Email section:** - Fixed import paths: `BaseEmail` lives at `/templates`, `OtpEmail` at `/otp`. Both were importing from the package root, which would fail at build time for consumers. - Rendering section rewritten to show the registry pattern: the renderer is name-keyed, templates are registered at construction time (or via `.register()`), and `render(name, props)` looks up by name. Removed the incorrect pass-component-directly pattern. **better-auth-resend section:** - `resendOTP` config shape was entirely wrong. Doc claimed `resendOTP(env)` with `env = { RESEND_API_KEY, FROM_EMAIL }`, plus an optional second argument for branding. Actual is a single `ResendOTPConfig` object: `{ apiKey, fromEmail, brandName, logoUrl?, websiteUrl?, expiryMinutes?, baseUrl? }`. Rewrote integration example and documented the full config shape. **Custom adapter example:** - Replaced `class PostmarkProvider implements EmailProvider` with `createPostmarkProvider(config): EmailProvider` factory closure to match the monorepo factory-pattern convention in CLAUDE.md. **Barrel exports section:** - Replaced 4 wrong imports (`ResendProvider`, `R2BlobStorage`, `handleEmailClassifyQueue`, `ClassifyEmailWorkflow`) with the actual exports via their correct subpaths. ## packages/better-auth-resend/README.md (fixing #76 drift on main) - `resendOTP` config: was `{ apiKey, from, appName }`, actual is `{ apiKey, fromEmail, brandName, ... }`. Fixed the example and the config table. Added the 4 optional fields (`logoUrl`, `websiteUrl`, `expiryMinutes`, `baseUrl`) the real schema exposes. ## packages/better-auth-resend/docs/usage.md (fixing #76 drift on main) - Same `resendOTP` config fix as the README. - "What the user sees" section updated: subject template is `"<brandName> verification code: <otp>"`, not `"Your <appName> code"`. - "Replacing the template" example now uses the actual renderer registry pattern and `provider.sendEmail` (not `.send`). ## packages/react-email/README.md (fixing #76 drift on main) - Renderer section now shows the registry pattern: `createReactEmailRenderer({ otp: OtpEmail })` then `renderer.render("otp", { code, brandName })`. The previous example called `render(OtpEmail({ otp, appName }))` which neither matches the interface nor uses the real OtpEmail prop names. - Fixed OtpEmail prop names: `otp` -> `code`, `appName` -> `brandName`, `expiresInMinutes` -> `expiryMinutes`. ## packages/react-email/docs/templates.md (fixing #76 drift on main) - Renderer section: same registry-pattern fix as the README, plus a "Why a registry instead of passing components" explanation (string-keyed templates let service code reference templates without importing React components). - OtpEmail props: same `code`/`brandName`/`expiryMinutes` fix. - "Writing your own template" example now registers the custom template with the renderer and calls `render(name, props)` instead of passing the component directly. ## Verification - pnpm test: 609 passing - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - pnpm format:check: clean on all modified files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 tasks
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
…on (#79) The JSDoc above `AuthAdapter` said "App passwords are stored hashed (argon2) in D1 and verified here." That's wrong on two axes: 1. The IMAP server does not own credential storage or hashing. That was the original design but it was rewritten: AuthAdapter is an interface contract only, the consumer brings their own auth system. Prescribing argon2 contradicts the rewritten contract. 2. Core docs must be generic: "blob storage" not "R2", "database" not "D1". Referencing D1 by name inside the core protocol package violates the zero-vendor-dependency principle. Replaced with a proper JSDoc block that: - States the interface-only posture explicitly - Names the consumer's ownership (storage, hashing, generation, revocation) - Lists the security guarantees the IMAP server DOES enforce regardless of adapter implementation (generic failure response, rate limit, disconnect after max attempts) - Refers to the adapter lifecycle in neutral language This matches the authoritative `authentication.md` doc shipped in PR #76 and the interface-only posture in the `@rafters/mail-imap-server` quickstart.md fixed in PR #78. Verification: - pnpm --filter @rafters/mail-imap test: 313 passing - pnpm --filter @rafters/mail-imap typecheck: clean - pnpm --filter @rafters/mail-imap build: ESM + dts clean - pnpm lint: 0 warnings, 0 errors Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
* docs(packages): fix interface-shape drift in 5 adapter docs After shipping the docs in PR #76 I spot-read them and found real interface drift -- types, method signatures, and import paths that did not match the current source. These would have broken every consumer that copy-pasted the examples. The @vault-2026 technical accuracy review covered the schema sections of core-reference.md but did not cover the adapter package docs, which is where these errors sit. - EmailProvider.sendEmail: type names wrong. Doc said `sendEmail(params: SendEmailParams): Promise<SendEmailResult>`. Actual is `sendEmail(params: EmailParams): Promise<{ id: string }>`. - EmailParams field table wrong: `from` is optional not required, `to` is a single string not array, `cc`/`bcc`/`headers` do not exist in EmailParams. - Documented the full EmailProvider surface (mailing lists, subscribers, campaigns, audiences), which was omitted. - Method calls wrong: doc called `mailService.compose(...)` and `mailService.replyToThread(...)`. Actual methods are `InboxEmailService.composeEmail` and `replyToThread`. - Thread metadata update: doc said `messageCount`, schema has `unreadCount`. - BlobStorage interface signatures wrong: missing `options?` on `put` and `get`, `get` return type was `string | undefined` (actual is `BlobObject | null`), and `generateKey` was missing entirely. - Added BlobObject, BlobPutOptions, and BlobGetOptions types alongside so consumers see the full shape. - InboundAdapter.handleIncoming return type wrong: said `Promise<void>`, actual is `Promise<{ messageId, threadId }>`. - "Inbound email structure" section described a 14-field parsed form as if it were the input type. The actual InboundEmail is 4 fields: `raw: ArrayBuffer`, `from`, `to`, `headers`. The parsed fields land on the message row AFTER the adapter does parsing work. Rewrote with a clear split between input shape and parsed-form schema fields. - Classification section: `ClassificationAdapter` should be `EmailClassifier`, `summary` field does not exist on `EmailClassification` (actual fields are `tags`, `priority`). - Interface name wrong: `ClassificationAdapter` should be `EmailClassifier`. - Method signature wrong: `classify(message: ClassificationInput)` should be `classify(from: string, subject: string, body: string)`. - Result type wrong: `ClassificationResult` with `summary` field should be `EmailClassification` with `tags` and `priority` fields. - Example classifier implementation updated to the correct shape. - Worker example imported `createR2BlobStorage` and `parseInboundEmail` from the package root. Actual exports are `createR2Storage` (from `/storage` subpath) and `parseEmailHeaders` / `hashContent` (from `/parsing` subpath). Also `createR2Storage` takes `{ bucket }` config, not a raw R2Bucket. - `blobStorage.put(parsed.rawEmail)` was missing the required `key` parameter. Rewrote to use `storage.generateKey` and `storage.put(key, raw)`. - `sendEmail` example showed `to: [array]`, actual is a single string. - "Next steps" link list pointed at 5 docs at `./classification.md`, `./imap-quickstart.md`, etc. Only `./classification.md` does not exist in this package -- the rest live in other packages. Replaced with per-package npm links. - pnpm test: 609 passing across 37 files (no code change, same count as before) - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - oxfmt normalized the 5 modified docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(core): fix accuracy drift in threading, migrations, newsletters Three more core docs had significant accuracy drift that would have misled consumers reading them on npm. These were NOT covered by the prior @vault-2026 schema review (which focused on core-reference.md). ## packages/core/docs/threading.md - The doc described a threading engine that matches messages to threads using In-Reply-To / References / subject fallback. That engine does not exist in the core threading module -- threading.ts only exports generateMessageId, buildReferences, and generateSnippet as header-building blocks. Thread matching is the inbound adapter's responsibility and has a TODO in threading.ts pointing to #13. - The doc claimed "subject matching within the same mailbox (configurable)". Grep for it -- no such feature exists. Subject matching is intentionally NOT part of the design because subject lines collide across unrelated conversations. - The doc claimed "Deleting a thread soft-deletes all its messages". Checked the schema: inbox_message.threadId has ON DELETE CASCADE, which is a hard-delete cascade, not soft-delete. Soft-deleting a thread (setting deletedAt) does NOT cascade to messages. Clarified. - The doc's thread model table was missing columns that exist in the schema: startedAt, updatedAt, archivedAt. Added. ## packages/core/docs/migrations.md - The doc said "13 tables across two domains" and listed 3 newsletter tables as if they were part of migrationSQL. Actually migrationSQL only contains 10 inbox tables. The 3 newsletter tables (platform_audience, platform_subscriber, broadcast_audit) are Drizzle schema exports but are NOT in migrationSQL and are not written to by any shipped service. - The newsletter table names were wrong: doc said mailing_list, subscriber, campaign. Actual names are platform_audience, platform_subscriber, broadcast_audit. - The doc said system folders are "initialized automatically" when a mailbox is created. They are not. The consumer has to explicitly call FolderService.initSystemFolders(mailboxId). - The "Conventions" section said "every table has deletedAt". Two label join tables (inbox_message_label, inbox_thread_label) do not have deletedAt because their delete semantic is direct row removal. ## packages/core/docs/newsletters.md - The entire schema section described a completely different design than what exists in newsletter.ts. The doc had tables mailing_list, subscriber, campaign with fields like status, metadata, scheduledAt, htmlBody -- none of which exist. Actual tables are platform_audience (providerListId, name, description, slug), platform_subscriber (userId, audienceId, providerSubscriberId, subscribedAt), and broadcast_audit (providerCampaignId, subject, contentHash, sentBy, audienceName, recipientCount, sentAt). - The doc described a "status lifecycle" (subscribed -> unsubscribed / bounced / complained) that does not exist because there is no status column on platform_subscriber. - The doc described a campaign lifecycle (draft -> scheduled -> sending -> sent) that does not exist because there is no status column on broadcast_audit (which is an audit log, not a campaign record). - Rewrote to accurately describe: (1) the EmailProvider surface as the authoritative API for mailing lists / subscribers / campaigns, (2) the 3 platform tables as OPTIONAL local mirror / audit tables the consumer may include in their own migrations, (3) that the provider (Resend) owns the authoritative data and the framework does not duplicate it locally by default. ## Verification - pnpm test: 609 passing - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - pnpm format:check: clean on modified files (.claude/settings.json is untracked session state, not part of this PR) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
…ct-email (#81) * docs: fix interface drift across adapters.md, better-auth-resend, and react-email Second pass of accuracy fixes after finding more drift while reviewing the 967-line adapters.md and cross-referencing against actual source. Some of these errors were also present in files I introduced in PR #76 (now on main) -- fixing those here too. ## packages/core/docs/adapters.md **Resend section:** - Replaced `new ResendService(...) + new ResendProvider(resend)` with the actual factory: `createResendProvider({ apiKey, fromEmail })`. `ResendProvider` is not an exported class; `createResendProvider` is a factory function. - `CampaignParams.audienceId` -> `listId`. The field name in the actual `campaignParamsSchema` is `listId`. - Replaced `new MockEmailProvider()` with `createMockEmailProvider()`. Mock is also a factory, not a class. - Documented the real mock surface: `sentEmails` as a top-level property, `getState()` for full internal state introspection, `clear()` for test resets. **Cloudflare section:** - Removed all references to `handleInboundEmail` (does not exist) and `R2BlobStorage` class (does not exist). Replaced with the actual building blocks: `createR2Storage`, `parseEmailHeaders`, `hashContent`. - Fixed the `BlobStorage` interface to match the real shape: single `put(key, content, options?)`, `get(key, options?)` returns `BlobObject | null`, `delete(key)`, `generateKey(contentHash, extension)`. The doc had `putRaw`/`putText`/`putHtml` three-method split, which is not how the interface is shaped. - Worker entry point example rewritten to use the actual exports and demonstrate the compose-your-own pattern (read bytes, parse headers, hash, store, insert row). - Thread matching: clarified it is the consumer's responsibility, not a feature of the adapter. The adapter provides the parsing building blocks; thread matching queries are the consumer's code. - `InMemoryBlobStorage` example rewritten as a factory function returning `BlobStorage` with the correct four methods. **Workers AI section:** - Removed `ClassifyEmailWorkflow` and `handleEmailClassifyQueue` references (do not exist). The package does not ship a workflow subpath or a queue subpath. - Replaced `createEmailClassifier` with the real factory name `createWorkersAIClassifier(ai, config?)`. - Queue consumer example rewritten to show consumer-implemented queue handling using the real classifier. - Mock AI type is now `AiBinding` from the package, not `Ai`. **React Email section:** - Fixed import paths: `BaseEmail` lives at `/templates`, `OtpEmail` at `/otp`. Both were importing from the package root, which would fail at build time for consumers. - Rendering section rewritten to show the registry pattern: the renderer is name-keyed, templates are registered at construction time (or via `.register()`), and `render(name, props)` looks up by name. Removed the incorrect pass-component-directly pattern. **better-auth-resend section:** - `resendOTP` config shape was entirely wrong. Doc claimed `resendOTP(env)` with `env = { RESEND_API_KEY, FROM_EMAIL }`, plus an optional second argument for branding. Actual is a single `ResendOTPConfig` object: `{ apiKey, fromEmail, brandName, logoUrl?, websiteUrl?, expiryMinutes?, baseUrl? }`. Rewrote integration example and documented the full config shape. **Custom adapter example:** - Replaced `class PostmarkProvider implements EmailProvider` with `createPostmarkProvider(config): EmailProvider` factory closure to match the monorepo factory-pattern convention in CLAUDE.md. **Barrel exports section:** - Replaced 4 wrong imports (`ResendProvider`, `R2BlobStorage`, `handleEmailClassifyQueue`, `ClassifyEmailWorkflow`) with the actual exports via their correct subpaths. ## packages/better-auth-resend/README.md (fixing #76 drift on main) - `resendOTP` config: was `{ apiKey, from, appName }`, actual is `{ apiKey, fromEmail, brandName, ... }`. Fixed the example and the config table. Added the 4 optional fields (`logoUrl`, `websiteUrl`, `expiryMinutes`, `baseUrl`) the real schema exposes. ## packages/better-auth-resend/docs/usage.md (fixing #76 drift on main) - Same `resendOTP` config fix as the README. - "What the user sees" section updated: subject template is `"<brandName> verification code: <otp>"`, not `"Your <appName> code"`. - "Replacing the template" example now uses the actual renderer registry pattern and `provider.sendEmail` (not `.send`). ## packages/react-email/README.md (fixing #76 drift on main) - Renderer section now shows the registry pattern: `createReactEmailRenderer({ otp: OtpEmail })` then `renderer.render("otp", { code, brandName })`. The previous example called `render(OtpEmail({ otp, appName }))` which neither matches the interface nor uses the real OtpEmail prop names. - Fixed OtpEmail prop names: `otp` -> `code`, `appName` -> `brandName`, `expiresInMinutes` -> `expiryMinutes`. ## packages/react-email/docs/templates.md (fixing #76 drift on main) - Renderer section: same registry-pattern fix as the README, plus a "Why a registry instead of passing components" explanation (string-keyed templates let service code reference templates without importing React components). - OtpEmail props: same `code`/`brandName`/`expiryMinutes` fix. - "Writing your own template" example now registers the custom template with the renderer and calls `render(name, props)` instead of passing the component directly. ## Verification - pnpm test: 609 passing - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - pnpm format:check: clean on all modified files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(architecture): fix package count, IMAP status, and consumer vs framework boundary The repo-level docs/architecture.md had accumulated significant drift from the current shipped state: 1. **Package count wrong.** Said "Six packages" -- it is nine now after the IMAP packages shipped this week (@rafters/mail-imap, @rafters/mail-imap-cloudflare, @rafters/mail-imap-server). Updated the count, the list, and the dependency graph. 2. **IMAP section said "designed, not built, planned for post-0.1.0."** All three IMAP packages ship in 0.1.0. Rewrote the section: - Status is "shipped in 0.1.0 across three packages" - Added the Node runtime alongside the DO runtime - Expanded the command set to match what is actually shipped: added UNSELECT (RFC 3691), COPY + MOVE (RFC 6851), APPEND (RFC 4315 APPENDUID). The previous list was the Phase 1 subset. - Added the advertised capability list from SERVER_CAPABILITIES - Added a transport comparison with the Spectrum future path (still blocked on Enterprise tier; issue #57) - Added the inbound signaling section covering both runtimes (DO: POST /notify, Node: server.notify() from PR #78) 3. **Invented exports in the "Extracted" list.** Replaced: - `ResendProvider` implementation -> `createResendProvider` factory - `MockEmailProvider` class -> `createMockEmailProvider` factory - `ClassifyEmailWorkflow` -> removed (does not exist) - Queue consumer `handleEmailClassifyQueue` -> removed (does not exist) - Added real exports: createImapDurableObject, createImapWorker, createImapServer, parseEmailHeaders, hashContent, classifier helper functions 4. **Wrong subpath import example.** `createR2BlobStorage` -> `createR2Storage`. Same bug as elsewhere. 5. **Inbound flow described as InboundAdapter doing the work.** The shipped adapters provide building blocks (parseEmailHeaders, hashContent, createR2Storage); the orchestration is consumer code. Rewrote the flow diagram with steps 5-8 clearly labeled as "Consumer-written" and added a callout explaining why there is no pre-baked handleInboundEmail (schema extensions + auth context + pipeline topology are all application decisions). 6. **Classification flow described as framework doing the work.** Steps 1-4 are the classifier (which ships in mail-workers-ai); steps 5-8 (DB update, spam folder move, label application) are consumer-written against their own schema and FolderService. Rewrote to make the boundary explicit. 7. **Threading logic described as shipped behavior.** Threading.ts exports generateMessageId, buildReferences, and generateSnippet as building blocks. Thread matching is NOT shipped -- it is the consumer's responsibility for inbound (no InboundAdapter implementation exists yet). Outbound threading in composeEmail/ replyToThread IS shipped via InboxEmailService. Clarified the split. No content additions that invent features. Every claim is verified against actual source in packages/*. ## Verification - pnpm test: 609 passing - pnpm typecheck: clean - pnpm lint: 0 warnings, 0 errors - pnpm format:check: clean - All referenced exports, method names, and command names verified against the actual TypeScript source Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(imap/commands): correct SEARCH criteria list to match parser The SEARCH command section listed 6 criteria that the actual parser does not implement: ANSWERED, DELETED, DRAFT, FLAGGED, SEEN, UNSEEN. Any IMAP client sending one of these receives "BAD Unknown search criterion: <name>" -- a hard failure, not a graceful fallback. Verified against packages/imap/src/protocol/parser.ts: the switch statement parseSearchCriteria has cases for ALL, NEW, FROM, TO, CC, BCC, SUBJECT, BEFORE, ON, SINCE, LARGER, SMALLER, TEXT, BODY, UID, NOT, OR plus bare sequence sets. Everything else throws via the default branch. Updated the supported list to reflect reality and added a "Not yet supported" section that: - Names the missing flag-based criteria explicitly - Names other RFC 3501 criteria that are also missing (KEYWORD, UNKEYWORD, HEADER, SENTBEFORE / SENTON / SENTSINCE) - Documents the practical workaround: SEARCH NEW matches RECENT + UNSEEN for the "new mail" UI case, and STATUS INBOX (UNSEEN) returns the unread count without needing the UNSEEN criterion. - Notes that this is a real protocol gap being tracked in an issue Updated the example to use FROM + SINCE (both supported) instead of UNSEEN + SINCE (UNSEEN is not supported). No implementation change in this PR. The parser gap is a separate issue that needs a code change to add the flag-based criteria to parseSearchCriteria and wire them to searchMessages in the message adapter. ## Verification - Verified every listed criterion against the case statements in parser.ts and the SearchCriterion type - pnpm format:check: clean - pnpm test: (unchanged) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
README.mdin every package so the npm package page renders properly instead of "no readme available"Why
Pre-publish check surfaced that none of the 9 packages had a README or a license field. npm package pages without a README look like abandoned or broken packages; consumers deciding whether to install a 0.1.0 framework dep need to see a purpose statement within three seconds of landing.
The READMEs
Each one follows the same shape:
Per-package highlights:
License field
`"license": "MIT"` added to every `package.json` after the `description` field. npm uses this for the license badge on the package page and for SPDX metadata. No LICENSE file in this repo -- the actual text is at the org level (`rafters-studio/.github`), which auto-links to all repos in the org.
Test plan
Stack
This PR chains off `chore/imap-build-pipeline` (#73) so all 9 packages have the tsup-updated `package.json` files as the base state. When #73 merges, this branch rebases cleanly onto main.