docs: fix interface drift across adapters.md, better-auth-resend, react-email#81
Merged
docs: fix interface drift across adapters.md, better-auth-resend, react-email#81
Conversation
… 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>
…ramework 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>
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>
This was referenced Apr 11, 2026
Open
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
The primary onboarding doc told consumers to import APIs that do
not exist. A new user following this guide hits import errors on
step 5 and never reaches a working Worker.
## Invented exports removed
- `createInboundHandler` from `@rafters/mail-cloudflare` -- does
not exist. The package ships building blocks
(`createR2Storage`, `parseEmailHeaders`, `hashContent`), not a
one-shot inbound handler. Rewrote step 5 ("Receive your first
email") to show the compose-your-own pattern: read bytes, parse
headers, hash content, store raw in R2, then the consumer's own
code for DB insert, thread matching, and classifier dispatch.
- `createR2BlobStorage` -- wrong name, actual is `createR2Storage`.
Three occurrences fixed (step 5, step 6 createMailService, and
the describe-the-handler section).
## Missing required config
- `createInboxEmailService` example was missing the `domain`
parameter. The config type requires `{ db, blobStorage,
emailProvider, domain }` -- domain is used by
`generateMessageId` to build Message-IDs. Added the field to
the createMailService example with a comment explaining why.
## Wrong signatures
- `resendOTP(env)` -> `resendOTP({ apiKey, fromEmail, brandName })`.
The config shape was wrong. Same fix as PR #81 for
better-auth-resend README.
## Package count drift
- Step 3 said migrations create "10 inbox tables plus the 3
newsletter tables." Only the 10 inbox tables are in
`migrationSQL`. The 3 newsletter tables are schema-only exports
(not in migrationSQL) and are not written to by any shipped
service -- they are optional platform-side tracking tables.
Clarified.
- Package map only listed 6 packages. Updated to 9 with the IMAP
packages (mail-imap, mail-imap-cloudflare, mail-imap-server)
added. Added a "What's next: IMAP" section that points at
deploying the standard-client runtime.
## Minor
- Workers AI section said classifier "runs as a Cloudflare
Workflow or Queue consumer." Neither exists in the shipped
package. Rewrote to describe inline classification + noting
that async queue/workflow wiring is consumer-implemented.
- replyToThread step 7 said "Moves the thread to the 'sent'
folder snapshot." Reply does not move folders -- the thread
stays in its current folder, replyToThread only updates
unreadCount, lastMessageAt, and the snippet. Corrected.
## Verification
- pnpm test: 609 passing
- pnpm typecheck: clean
- pnpm lint: 0 warnings, 0 errors
- pnpm format:check: clean
- Every referenced export cross-checked against the actual
source files in packages/*
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
… has full flag support Self-correction. On PR #81 I added a "Not yet supported" section listing ANSWERED, DELETED, DRAFT, FLAGGED, SEEN, UNSEEN as missing, based on an incomplete read of parser.ts. I grepped for `case "` and saw 17 switch statement entries, concluding the flag criteria were unsupported. I missed the FLAG_CRITERIA lookup table earlier in the same function that dispatches flag tokens BEFORE the switch: const FLAG_CRITERIA: Record<string, { flag: string; negated: boolean }> = { ANSWERED, DELETED, DRAFT, FLAGGED, SEEN, RECENT, UNANSWERED, UNDELETED, UNDRAFT, UNFLAGGED, UNSEEN, OLD }; All 12 flag criteria + `NEW` + `ALL` + the header / date / size / text / UID / NOT / OR criteria ARE supported. The parser tests at packages/imap/tests/protocol/parser.test.ts cover ANSWERED and DELETED / UNDELETED explicitly. The original doc in vault had the full list correct. PR #81's "fix" removed correct information and replaced it with an incorrect gap claim. This commit restores the correct list and adds explicit reference to the FLAG_CRITERIA table + the parser test coverage so a future reader can audit the claim against source without making the same mistake. Also closing issue #82 (the "missing SEARCH criteria" gap I filed based on the same mistake) as invalid. The actual remaining gaps are narrower: KEYWORD, UNKEYWORD, HEADER, SENTBEFORE / SENTON / SENTSINCE. Those are accurately flagged as "Not yet supported" in this revision. ## How this slipped through Per my own memory rule "Before recommending from memory, verify the file still exists" -- I verified against source but only partially. Next time: read the entire parser function, not just the switch statement. Lookup tables can live above the switch and short-circuit before any case hits. ## Verification - Verified FLAG_CRITERIA contents against packages/imap/src/protocol/parser.ts - Verified parser tests at packages/imap/tests/protocol/parser.test.ts:326-339 - pnpm test: 614 passing (unchanged) - pnpm lint: 0 warnings, 0 errors - pnpm format:check: clean Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ssilvius
added a commit
that referenced
this pull request
Apr 11, 2026
… has full flag support (#84) Self-correction. On PR #81 I added a "Not yet supported" section listing ANSWERED, DELETED, DRAFT, FLAGGED, SEEN, UNSEEN as missing, based on an incomplete read of parser.ts. I grepped for `case "` and saw 17 switch statement entries, concluding the flag criteria were unsupported. I missed the FLAG_CRITERIA lookup table earlier in the same function that dispatches flag tokens BEFORE the switch: const FLAG_CRITERIA: Record<string, { flag: string; negated: boolean }> = { ANSWERED, DELETED, DRAFT, FLAGGED, SEEN, RECENT, UNANSWERED, UNDELETED, UNDRAFT, UNFLAGGED, UNSEEN, OLD }; All 12 flag criteria + `NEW` + `ALL` + the header / date / size / text / UID / NOT / OR criteria ARE supported. The parser tests at packages/imap/tests/protocol/parser.test.ts cover ANSWERED and DELETED / UNDELETED explicitly. The original doc in vault had the full list correct. PR #81's "fix" removed correct information and replaced it with an incorrect gap claim. This commit restores the correct list and adds explicit reference to the FLAG_CRITERIA table + the parser test coverage so a future reader can audit the claim against source without making the same mistake. Also closing issue #82 (the "missing SEARCH criteria" gap I filed based on the same mistake) as invalid. The actual remaining gaps are narrower: KEYWORD, UNKEYWORD, HEADER, SENTBEFORE / SENTON / SENTSINCE. Those are accurately flagged as "Not yet supported" in this revision. ## How this slipped through Per my own memory rule "Before recommending from memory, verify the file still exists" -- I verified against source but only partially. Next time: read the entire parser function, not just the switch statement. Lookup tables can live above the switch and short-circuit before any case hits. ## Verification - Verified FLAG_CRITERIA contents against packages/imap/src/protocol/parser.ts - Verified parser tests at packages/imap/tests/protocol/parser.test.ts:326-339 - pnpm test: 614 passing (unchanged) - pnpm lint: 0 warnings, 0 errors - pnpm format:check: clean 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
Second pass of doc accuracy fixes after reading the 967-line `packages/core/docs/adapters.md` top to bottom and cross-referencing every claim against the actual source. Found significant drift: invented exports, wrong factory names, wrong field names, wrong interface shapes, wrong config schemas.
Some of the same errors existed in docs I introduced in PR #76 (now merged to main) -- fixing those here too rather than opening a separate PR.
The errors
adapters.md -- Resend section
adapters.md -- Cloudflare section
adapters.md -- Workers AI section
adapters.md -- React Email section
adapters.md -- better-auth-resend section
adapters.md -- Custom adapter example
adapters.md -- Barrel exports section
better-auth-resend README + docs/usage.md (drift I introduced in #76)
react-email README + docs/templates.md (drift I introduced in #76)
Why these slipped past the previous accuracy sweep
PR #80 fixed schema drift that @vault-2026 had already flagged (22 items + 3 misleading in `core-reference.md`). That review focused on the schema sections of the core doc. The 967-line `adapters.md` and the per-package usage docs were never audited. This PR closes that gap.
Verification
Scope
Single commit, 5 files, all documentation. No runtime behavior change.