209 lines
6.5 KiB
Markdown
209 lines
6.5 KiB
Markdown
# 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<string, string[]>
|
|
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<ParsedEmail>
|
|
|
|
// Parse headers only (no body decode — fast path)
|
|
function parseHeaders(raw: string | Uint8Array): Map<string, string[]>
|
|
|
|
// 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<Uint8Array>
|
|
readable: ReadableStream<EmailParseEvent>
|
|
}
|
|
|
|
type EmailParseEvent =
|
|
| { type: 'headers'; headers: Map<string, string[]> }
|
|
| { 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.
|