3.1 KiB
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 (perencoding), or plain UTF-8 string — falls back toUint8Array(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 asEMAIL_PROCESSING.HASH_FAILUREwith the underlying error oncause. - OTel counters for processed vs rejected attachments (attributed by reason) and a duration histogram for hash + metadata extraction.
Usage: Basic
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
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?)— returnsProcessedAttachment.processAttachments(list, options?)— returnsProcessedAttachment[].
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.