MailglassInbound.MIME (MailglassInbound v1.6.0)

Copy Markdown View Source

Standalone, never-raising RFC 5322 MIME parser.

parse/1 turns a canonical raw MIME body into a stable internal representation, or returns a structured MailglassInbound.MIMEError. It never raises: the underlying gen_smtp mimemail decoder escapes through three mechanisms (erlang:error, throw, and :exit/:undef from iconv), all of which are absorbed by the Mailglass.OptionalDeps.GenSmtp gateway seam and translated into {:error, %MailglassInbound.MIMEError{}}.

Contract

@spec parse(binary()) :: {:ok, repr} | {:error, MailglassInbound.MIMEError.t()}
  • {:ok, repr}repr is %{headers: ..., parts: ..., attachments: ..., inline: ...} (see Internal representation below).
  • {:error, %MIMEError{type: :inbound_mime_invalid}} — the raw source could not be parsed (any of the three escape mechanisms, or the representation exceeded :max_depth). :cause carries the tagged gateway failure; :context carries %{byte_size: byte_size(raw)}.
  • {:error, %MIMEError{type: :gen_smtp_unavailable}} — the optional gen_smtp dependency is not loaded; MIME parsing is unavailable (degraded fallback).

Note

The :max_depth option bounds the depth of the internal representation walkcollect_leaves/3 re-walking the already-decoded tree — and gives a deterministic structured ceiling on what the pipeline iterates. It does not limit the underlying :mimemail decoder recursion: decode_and_build/2 calls the decoder first, which fully parses to any depth before the guard ever runs. So :max_depth does not by itself defend against provider-fed deep-nesting (boundary-bomb) DoS.

Provider-fed DoS hardening — a real decoder-level recursion limit — is a A future concern that will plug into this same seam. The guard is kept because it is that seam and because it bounds the representation the pipeline iterates.

Internal representation

parse/1 returns a map with four keys:

  • :headers — the top-level decoded header proplist ([{binary, binary}]).
  • :parts — a flattened list of non-attachment, non-inline leaf parts. Each part is %{type, subtype, headers, params, body} where body is the raw (untranscoded) leaf bytes.
  • :attachments — leaf parts whose Content-Disposition is attachment. Each carries a resolved :filename (disposition_params["filename"] || content_type_params["name"]).
  • :inline — leaf parts whose Content-Disposition is explicitly inline and which carry a filename (typically Content-ID images). Inline text leaves without a filename land in :parts.

Standalone — not wired into any provider path

This parser is the producer only. It is NOT wired into the working JSON-based Postmark/SendGrid normalize paths in this version; Mailgun/SES raw-MIME ingress is the first consumer.

Encoding note

The gateway passes {:encoding, :none} to the decoder (gen_smtp does not bundle iconv), so leaf :body bytes are not transcoded to UTF-8. A consumer that needs UTF-8 text must transcode using the part's declared charset (content_type_params["charset"]). Flagged as a future improvement.

Summary

Types

A single decoded leaf or container part in the internal representation.

The stable internal representation returned by parse/1.

Functions

Parses a raw RFC 5322 MIME body. See the moduledoc for the full contract.

Parses a raw RFC 5322 MIME body with options.

Types

part()

@type part() :: %{
  type: binary(),
  subtype: binary(),
  headers: [{binary(), binary()}],
  params: map(),
  body: term()
}

A single decoded leaf or container part in the internal representation.

repr()

@type repr() :: %{
  headers: [{binary(), binary()}],
  parts: [part()],
  attachments: [part()],
  inline: [part()]
}

The stable internal representation returned by parse/1.

Functions

parse(raw)

(since 0.2.0)
@spec parse(binary()) :: {:ok, repr()} | {:error, MailglassInbound.MIMEError.t()}

Parses a raw RFC 5322 MIME body. See the moduledoc for the full contract.

Equivalent to parse(raw, []).

parse(raw, opts)

(since 0.2.0)
@spec parse(
  binary(),
  keyword()
) :: {:ok, repr()} | {:error, MailglassInbound.MIMEError.t()}

Parses a raw RFC 5322 MIME body with options.

Options

  • :max_depth — maximum multipart nesting depth before the representation-depth guard trips and returns :inbound_mime_invalid (default 100). See the moduledoc note on what this guard does and does not bound.
  • :gen_smtp_available? — overrides the gateway availability check (testing seam for the degraded path). Defaults to Mailglass.OptionalDeps.GenSmtp.available?/0.