# attachment-processor _(no submodules)_ ## Purpose Normalises and validates `EmailAttachment` payloads emerging from `parseEmail`. Each attachment is decoded to bytes, validated against a caller-supplied size + MIME-type allowlist, hashed with SHA-256 over the Web Crypto API, and packaged as a `ProcessedAttachment` carrying the original metadata plus a deterministic content hash for dedup, provenance, and dehydrated-template workflows. ## Features - Three input shapes: `ArrayBuffer`, base64 string (per `encoding`), or plain UTF-8 string — falls back to `Uint8Array(0)` for unknown shapes (matches upstream behaviour) so callers do not see crashes on rare attachment encodings. - Size limit defaults to 25 MiB; overrides validated via Zod. - MIME allowlist supports both exact match (`application/pdf`) and wildcard (`image/*`). - SHA-256 via `crypto.subtle.digest` — works in browsers, Node, Bun and Deno without polyfills. Hash failures surface as `EMAIL_PROCESSING.HASH_FAILURE` with the underlying error on `cause`. - OTel counters for processed vs rejected attachments (attributed by reason) and a duration histogram for hash + metadata extraction. ## Usage: Basic ```ts import { processAttachment } from "@suaveplan/email-processing/attachments"; const out = await processAttachment(parsed.attachments[0]); if (out.valid) { await store({ hash: out.metadata.contentHash, bytes: out.content }); } ``` ## Usage: Advanced ```ts import { processAttachments, type ProcessedAttachment, } from "@suaveplan/email-processing/attachments"; const results = await processAttachments(parsed.attachments, { maxSize: 5 * 1024 * 1024, allowedMimeTypes: ["image/*", "application/pdf"], }); const valid = results.filter((r): r is ProcessedAttachment => r.valid); const rejected = results.filter((r) => !r.valid); log.info({ valid: valid.length, rejected: rejected.length, hashes: valid.map((v) => v.metadata.contentHash), }); ``` ## API Reference - `AttachmentMetadata` — filename, size, MIME type, disposition, contentHash (hex SHA-256), optional contentId + description. - `ProcessedAttachment` — `{ metadata, content, valid, error? }`. - `AttachmentProcessorOptions` — `{ allowedMimeTypes?, maxSize? }`, Zod-validated. - `processAttachment(att, options?)` — returns `ProcessedAttachment`. - `processAttachments(list, options?)` — returns `ProcessedAttachment[]`. Errors: `INVALID_OPTIONS` for bad options, `HASH_FAILURE` if the Web Crypto API throws (e.g. in environments where the digest is unavailable). ## Implementation Notes `crypto.subtle.digest` requires the source buffer to satisfy the `BufferSource` type; passing a fresh `Uint8Array` works in every runtime tested. The duration histogram uses `performance.now()` when available and falls back to `Date.now()` for environments without it (older Node test isolates). Errors surfaced inside `extractMetadata` are propagated upward by `processAttachment` without being swallowed, so callers do not lose root-cause information when hashing fails.