An email's structure (MIME, RFC 2045 to 2049): its parts, their headers and their bodies, still encoded.
part = Letterbox.MIME.parse(raw)
part.type #=> "multipart/mixed"
Enum.map(part.parts, & &1.type)
#=> ["multipart/alternative", "image/jpeg"]Parsing copies nothing: each part's :body is a sub-binary of raw,
the bytes where they are. A body is decoded only when it is asked for:
a text with text/1, any body with decode/1, and an attachment as a
stream of chunks with stream/2, so a photograph of 8 MB goes to a file
without its decoded bytes ever all in memory.
The parser takes what mail clients send: LF as well as CRLF, a
multipart with no closing boundary, a part with no headers, a boundary
in quotes or not. It stops where an email would make it work without
end: past :max_depth levels of parts in parts (20) or :max_parts
parts (1,000), the rest of a multipart is one part of its type, not
split.
Summary
Functions
A part's body, decoded from its transfer encoding: the bytes it carries.
Every part with no parts of its own (the texts, images, files), in the
order a reader meets them, a message/rfc822 included as one.
The structure of an email, raw as it came (RFC 5322).
A part's body, decoded, as a stream of chunks: for a large attachment,
written to a file as it is decoded. :chunk is how many bytes of the
encoded body each chunk is decoded from (64 KB).
A text part's body in UTF-8, from its charset, with its line breaks as
\n: {:ok, text}, or {:lossy, text} when bytes of it could not be
read (see Letterbox.Charset).
Types
@type t() :: %Letterbox.MIME{ body: binary(), content_id: String.t() | nil, disposition: String.t() | nil, disposition_params: %{required(String.t()) => String.t()}, encoding: String.t(), headers: [Letterbox.MIME.Header.t()], params: %{required(String.t()) => String.t()}, parts: [t()], path: [non_neg_integer()], type: String.t() }
A part:
:type- its content type, in lowercase ("text/plain"when it has none, as RFC 2045 says).:params- the content type's parameters ("charset","boundary","name"), decoded.:disposition-"attachment","inline", or nil when it has none, and:disposition_params("filename", decoded).:encoding- itsContent-Transfer-Encoding, in lowercase:"base64","quoted-printable","7bit","8bit","binary".:content_id- itsContent-ID, without the angle brackets: what an HTML part'scid:points to.:headers- all its headers (Letterbox.MIME.Header.parse/1).:body- its body, still encoded, a sub-binary of the email.:parts- the parts of a multipart, and the one email of amessage/rfc822(a forwarded email, attached).:path- where it is:[]for the email,[1, 0]for the first part of the second.
Functions
A part's body, decoded from its transfer encoding: the bytes it carries.
Every part with no parts of its own (the texts, images, files), in the
order a reader meets them, a message/rfc822 included as one.
The structure of an email, raw as it came (RFC 5322).
Options: :max_depth (20) and :max_parts (1,000).
@spec stream(t(), keyword()) :: Enumerable.t()
A part's body, decoded, as a stream of chunks: for a large attachment,
written to a file as it is decoded. :chunk is how many bytes of the
encoded body each chunk is decoded from (64 KB).
A text part's body in UTF-8, from its charset, with its line breaks as
\n: {:ok, text}, or {:lossy, text} when bytes of it could not be
read (see Letterbox.Charset).