Menu

Getting started

Install

npm install @mvrx/mail @mvrx/wbxml

@mvrx/wbxml is only needed if you’re working with Exchange ActiveSync payloads — @mvrx/mail alone is enough for parsing inbound email.

The parse() Worker example

This is the canonical example from the @mvrx/mail README — a Cloudflare Email Worker that parses an inbound message and forwards its AI-ready content to an agent endpoint:

import { parse, wrappers } from "@mvrx/mail";

export default {
  async email(message: ForwardableEmailMessage) {
    const email = await parse(message, {
      wrapper: wrappers.xml("email"),
    });

    await fetch("https://agent.example.com/inbox", {
      method: "POST",
      body: JSON.stringify({
        messageId: email.messageId,
        threadId: email.threadId,
        from: email.metadata.from,
        subject: email.metadata.subject,
        input: email.content.forAI,
      }),
    });
  },
};

A single parse() call returns a fully normalized NormalizedEmail object — no configuration required. wrappers.xml("email") wraps content.forAI in an <email>...</email> tag, which is the recommended delimiter style for Claude and other models that follow XML-structured instructions (see Threads & wrappers).

What parse() accepts

function parse(
  source: ForwardableEmailMessage | ReadableStream<Uint8Array> | string,
  options?: ParseOptions,
): Promise<NormalizedEmail>

source can be a Cloudflare ForwardableEmailMessage, a raw RFC 5322 string, or a ReadableStream<Uint8Array> — so the same parser works identically on Cloudflare Workers, Node.js, Deno, and Bun; it doesn’t require the Cloudflare Email Routing product to run.

Reading attachments

Attachment bytes are never loaded during parse() — they’re lazy. Loop and call content() only for the attachments you actually need:

for (const att of email.attachments) {
  if (att.size > 10 * 1024 * 1024) continue; // skip > 10 MB
  const bytes = await att.content();
  // ... store bytes wherever you need them
}

Options you’ll actually use today

Option Default Purpose
wrapper none Delimiter wrapper applied to content.forAI (see Threads & wrappers)
cleaner built-in heuristic Replace the default quote/signature stripper with your own
onAttachment none Callback invoked per attachment during parsing
threadIdResolver AECS-1 §5 algorithm Override threadId calculation entirely
maxBodyBytes 10_000_000 Max bytes read from the message source
forAIMaxChars 8_000 Max characters in content.forAI before truncation

The full ParseOptions reference — including options that are part of the AECS-SDK-1 roadmap and not yet implemented — is documented in the AECS-SDK-1 specification.