Files
2026-05-27 23:29:53 +01:00

6.5 KiB

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

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

// 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

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

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.