5.0 KiB
5.0 KiB
Tasks: enterprise-server-emailer
Change ID: enterprise-server-emailer
Package: @suaveplan/emailer
Target path: server/services/emailer
Prerequisites on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/retry, @suaveplan/suppression, @suaveplan/idempotency, @suaveplan/templates, @suaveplan/logger
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold
- Create directory tree per
proposal.md. package.jsonat0.1.0,"sideEffects": false, exports"."+emailer+providers/in-memory+providers/smtp.- Deps: all workspace + catalog deps listed in proposal.
- DevDeps:
vitest catalog:testing,@vitest/coverage-istanbul catalog:testing,@suaveplan/testing workspace:^. tsconfig.jsonextendinglibrary-node.json;vitest.config.tswithcreateTestConfig().- Scripts:
lint,typecheck,test,test:coverage,build,docs:check.
2. Types and errors
- Define all interfaces and Zod schemas (
EmailMessageSchema,EmailerOptionsSchema,EmailAttachmentSchema) insrc/types/emailer.types.ts. - Write
emailer.types.md(≥200 words, both examples, API reference). src/error-codes.ts: six codes;registerEmailerErrorCodes(); eager call.
3. InMemory provider
- Implement
InMemoryProviderinsrc/providers/in-memory-provider/in-memory-provider.ts:send(message): store in internalsent[]; return{ messageId: uuid, status: 'sent', accepted: message.to, rejected: [] }.getSent()/clearSent(): testing helpers.
- Write
in-memory-provider.test.ts: send + getSent; clearSent; attachment round-trip. - Write
in-memory-provider.md(≥200 words).
4. SMTP provider
- Implement
SmtpProviderinsrc/providers/smtp-provider/smtp-provider.ts:- Constructor creates nodemailer
Transporter. send(message): buildMail.OptionsfromEmailMessage; calltransporter.sendMail(); map result.- Attachment handling: convert
Uint8ArraytoBufferfor nodemailer. - Throw
EMAILER_PROVIDER_UNAVAILABLEon transport error;EMAILER_SEND_FAILEDon send error. - Guard nodemailer import; throw if unavailable.
- Constructor creates nodemailer
smtp-provider.types.tswithSmtpProviderOptions.- Write
smtp-provider.test.ts(≤500 lines): skip ifSKIP_SMTP=1; usesmtp-testerornodemailer-mock; send and verify received; attachments;{ timeout: 15000 }. - Write
smtp-provider.md(≥200 words).
5. Emailer facade
- Implement
Emailerinsrc/emailer/emailer/emailer.ts:send(message):- Validate
toaddresses (basic@check); throwEMAILER_INVALID_ADDRESSon invalid. - Validate attachment sizes against configurable limit (default 25 MB each); throw
EMAILER_ATTACHMENT_TOO_LARGE. - Check suppression for each recipient; build filtered
acceptedlist; throwEMAILER_SUPPRESSEDif all suppressed. - If
templateIdprovided: render via@suaveplan/templates; populatehtml/text. - Idempotency guard when
idempotencyKeypresent. provider.send()with@suaveplan/retry; throwEMAILER_SEND_FAILEDon exhaustion.- OTel span
emailer.send; counteremailer.sends_totalwithstatus.
- Validate
sendBatch(messages): parallel withPromise.allSettled.
- Write
emailer.types.ts. - Write
emailer.test.ts(≤500 lines): successful send; suppressed recipient; invalid address; template rendering; attachment too large; retry; idempotency dedup; batch with partial failures. - Write
emailer.md(≥200 words, simple + advanced, API reference).
6. Integration tests
tests/integration.test.ts:Emailer+InMemoryProvider+ real@suaveplan/suppressionstore; add address to suppression; send batch; verify sent/suppressed split;{ timeout: 10000 }.
7. Docs + verify + publish
- Root
README.md: quick-start with InMemoryProvider, SMTP setup, suppression workflow, template usage, API table. bunx turbo lint --filter=@suaveplan/emailer— zero errors.bunx turbo typecheck --filter=@suaveplan/emailer— zero errors.bunx turbo test --filter=@suaveplan/emailer— all pass.bunx turbo test:coverage --filter=@suaveplan/emailer— 100%.bun scripts/publish.ts --package @suaveplan/emailer.- Update
CLAUDE.md; archive toopenspec/archive/2026-04-24-enterprise-server-emailer/.