Packages
email_parser
0.1.0
Pure Elixir RFC 5322 / MIME email parser that extracts nested attachments
Current section
Files
Jump to
Current section
Files
email_parser
README.md
README.md
# EmailParser
Pure Elixir RFC 5322 / MIME email parser that extracts nested attachments.
No native code, no dependencies.
```elixir
{:ok, attachments} = EmailParser.extract_nested_attachments(raw_message)
[
%EmailParser.Attachment{
name: "example.pdf",
content_type: "application/pdf",
content_bytes: <<...>>
}
] = attachments
```
`EmailParser.extract_nested_attachments/1` parses a raw message on a
best-effort basis and returns every attachment, recursing into nested
`message/rfc822` parts (forwarded messages). It returns `:error` only when the
input contains no parseable headers at all, as every function here does.
## Installation
Add `email_parser` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:email_parser, "~> 0.1.0"}
]
end
```
Documentation is published at <https://hexdocs.pm/email_parser>.
## Writing the attachments out
`EmailParser.extract_attachments_to_disk/2` writes them instead of returning their bytes,
and answers with the names of the files it wrote:
```elixir
{:ok, ["msg7-example.pdf"]} =
EmailParser.extract_attachments_to_disk(raw_message,
directory: "/tmp/inbox",
prefix: "msg7-",
mime_types: ["application/pdf"]
)
```
The directory is created when missing and defaults to the current one; the
prefix keeps attachments of different messages apart in a shared directory;
`:mime_types` restricts what is written, and defaults to everything. Only the
last path segment of an attachment's name is used, so a message cannot write
outside the directory, and a failed write takes the files already written down
with it.
## Reading the message itself
`EmailParser.strip_attachments/1` answers with the message minus its attachments — the
header fields and the bodies:
```elixir
{:ok, email} = EmailParser.strip_attachments(raw_message)
%EmailParser.Email{
headers: %{"from" => "bob@example.com", "subject" => "Orçamento"},
text_body: "Segue o orçamento.",
html_body: "<p>Segue o orçamento.</p>"
} = email
```
Header names are downcased, since RFC 5322 field names are case-insensitive,
their values are RFC 2047 decoded, and the first occurrence of a repeated
field wins. `Content-Transfer-Encoding` and `Content-Disposition` are left
out: they describe the raw body, and the bodies here are decoded.
`text_body` is always plain text — a message carrying only HTML is rendered to
text, with markup dropped, block elements broken onto their own line and
character references resolved. `html_body` is set only when the message
actually carries an HTML part.
## What it handles
* multipart trees of any shape (`mixed`, `alternative`, `related`, `digest`,
`signed`, ...): text parts that render as the message body are not reported
as attachments, while everything else — inline images included — is;
* base64 and quoted-printable transfer encodings, falling back to the raw
bytes when a part is incorrectly encoded;
* RFC 2047 encoded words and RFC 2231 extended parameters/continuations in
attachment names, and the raw 8-bit bytes non-conforming mailers write
there, read as UTF-8 when they form valid UTF-8 and as ISO-8859-1 otherwise;
* charset conversion to UTF-8 for UTF-8/ASCII, ISO-8859-1, windows-1252 and
UTF-16 text (other charsets are kept with unmappable bytes replaced by
`U+FFFD`);
* nested `message/rfc822` attachments, unencoded or encoded, with encoded
nesting limited to 3 levels;
* any binary as input — invalid UTF-8 and malformed MIME structures are
handled without raising.
## Testing
Besides its unit suite, the parser runs against a vendored corpus of 95
real-world messages — RFC samples, output of historical mail clients,
charset-heavy messages and deliberately malformed ones — each with a golden
file describing the expected attachments. See
[test/fixtures/corpus](https://github.com/saasformula/email_parser/tree/main/test/fixtures/corpus).
Every corpus message is also run through `extract_attachments_to_disk/2` and
`strip_attachments/1`.
## License
MIT — see [LICENSE](LICENSE). The vendored test corpus originates from the
mail-parser project by Stalwart Labs (Apache-2.0 OR MIT); see
[test/fixtures/corpus/README.md](https://github.com/saasformula/email_parser/blob/main/test/fixtures/corpus/README.md).
The corpus is not part of the Hex package.