# OpenSpec Change: enterprise-email-processing - **Change ID**: enterprise-email-processing - **Package**: `@suaveplan/email-processing` - **Category**: universal - **Target path**: `universal/email/email-processing` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/email-processing.md` --- ## Summary Deliver `@suaveplan/email-processing` on the genesis branch from scratch at enterprise quality. The package provides MIME email parsing and processing: raw RFC 5322/MIME message parsing, header field extraction and folding, multipart boundary handling, HTML-to-text extraction, inline attachment enumeration, address list parsing (RFC 5321/5322), email address validation with disposable-domain detection, and a streaming parse API over `ReadableStream`. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for every public type, OTel instrumentation, `AbortSignal` cancellation on async paths, property-based tests with fast-check, and a benchmark suite. --- ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base class for `EmailProcessingError`; `defineCodes` for `EMAIL_PROC.*` codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` (injectable clock for header date parsing) | | `@suaveplan/validation` (`workspace:^`) | Schema composition helpers for address validation | | `zod` (`catalog:forms`) | Zod schemas for all public option types and parsed output shapes | | `@opentelemetry/api` (`catalog:observability`) | Spans + counters on parse, validate, extract operations | | `tslib` (`catalog:build`) | Runtime helpers | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`). --- ## Public API surface ### Core types ```ts interface ParsedEmail { messageId?: string from: EmailAddress[] to: EmailAddress[] cc: EmailAddress[] bcc: EmailAddress[] replyTo: EmailAddress[] subject?: string date?: Date headers: Map textBody?: string htmlBody?: string attachments: Attachment[] inlines: Attachment[] } interface EmailAddress { name?: string address: string } interface Attachment { filename?: string contentType: string contentId?: string content: Uint8Array size: number } interface ParseOptions { maxSize?: number // default 25 MB allowPartial?: boolean // default false signal?: AbortSignal } ``` ### Functions ```ts // Parse raw RFC 5322 message bytes or string function parseEmail(raw: string | Uint8Array, options?: ParseOptions): Promise // Parse headers only (no body decode — fast path) function parseHeaders(raw: string | Uint8Array): Map // RFC 5321/5322 address list parser function parseAddressList(value: string): EmailAddress[] // HTML body to plain-text extraction function htmlToText(html: string, options?: HtmlToTextOptions): string // Email address validation (syntax + MX-optional + disposable-domain check) function validateAddress(address: string, options?: ValidateOptions): ValidationResult // Streaming parse — emits header, textBody, htmlBody, attachment events function createEmailStream(options?: ParseOptions): EmailParseStream interface EmailParseStream { writable: WritableStream readable: ReadableStream } type EmailParseEvent = | { type: 'headers'; headers: Map } | { type: 'textBody'; text: string } | { type: 'htmlBody'; html: string } | { type: 'attachment'; attachment: Attachment } ``` ### Error codes ```ts type EmailProcessingErrorCode = | 'EMAIL_PROC_PARSE_FAILED' | 'EMAIL_PROC_TOO_LARGE' | 'EMAIL_PROC_INVALID_ADDRESS' | 'EMAIL_PROC_INVALID_HEADER' | 'EMAIL_PROC_ENCODING_FAILED' | 'EMAIL_PROC_ABORTED' ``` --- ## Genesis additions 1. **`EmailProcessingError`** extends `SuaveplanError` with six stable error codes. 2. **Zod schemas** for `ParsedEmail`, `EmailAddress`, `Attachment`, `ParseOptions`, `ValidateOptions`. 3. **`AbortSignal` support** on `parseEmail` and `createEmailStream` — throws `EMAIL_PROC_ABORTED` on cancel. 4. **OTel instrumentation** — span `email.parse`, counter `email.parse_total`, histogram `email.parse_duration_ms`, attribute `email.attachment_count`. 5. **Disposable-domain detection** — bundled list of ~3 000 known disposable domains; `validateAddress` checks against it when `options.checkDisposable` is true. 6. **Property-based tests** — RFC 5322 header fold/unfold round-trip, address list round-trip, HTML-to-text idempotency on plain text input. 7. **Benchmark suite** — parse a 50 KB multipart message (cold), header-only parse (hot), 1 000-address list parse. --- ## File layout ```text universal/email/email-processing/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts # MAX_EMAIL_SIZE, CRLF, MIME_BOUNDARY_RE types/ common.ts common.md core/ parser/ parser.ts # parseEmail orchestrator parser.md parser.types.ts parser.test.ts index.ts header-parser/ header-parser.ts # parseHeaders, header fold/unfold header-parser.md header-parser.test.ts index.ts mime-decoder/ mime-decoder.ts # multipart, base64, QP decoding mime-decoder.md mime-decoder.test.ts index.ts address-parser/ address-parser.ts # parseAddressList address-parser.md address-parser.test.ts index.ts validation/ address-validator/ address-validator.ts # validateAddress + disposable-domain list address-validator.md address-validator.test.ts index.ts extraction/ html-to-text/ html-to-text.ts html-to-text.md html-to-text.test.ts index.ts streaming/ email-stream/ email-stream.ts # createEmailStream email-stream.md email-stream.test.ts index.ts __benchmarks__/ email-processing.bench.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream consumers**: `@suaveplan/campaigns`, `@suaveplan/delivery-tracking`, `@suaveplan/notification-channel`. **Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/validation` must be published to Verdaccio before building.