enmime: a MIME encoder and decoder for Go that takes mail as it comes
MIME mail encoding and decoding package for Go
At a glance
- What is it?
- enmime is a Go library for building and parsing MIME encoded email, developed alongside the Inbucket mail service, with a fluent builder for constructing messages and a parser that copes with the encoders people actually send. Its own documentation admits that a good number of MIME encoders in the wild are buggy, and that is the design constraint everything follows from.
- Who is it for?
- enmime suits a Go service that has to build outgoing mail, parse mail that arrived from anywhere, or both, and it does not suit a project that needs a full mail client, since it handles encoding and structure rather than mailbox storage, search or IMAP.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Built for the mail that actually arrives
The readme is blunt about the problem enmime exists to solve. enmime is a MIME encoding and decoding library for Go, focused on generating and parsing MIME encoded emails, and the development status note says it is production quality, but there are many buggy MIME encoders in the wild, so you may still encounter messages it cannot parse.
That sentence is the whole design brief. A library that only ever sees well formed input from one reference implementation has it easy. A library that has to survive a mailbox filled by twenty years of client software has to be forgiving in one direction and strict in the other. The parse side is where the tolerance lives, and the build side is where the specification is followed exactly.
The package is also being developed in tandem with the Inbucket email service, which is the useful context for the whole project. Inbucket is a mail testing service, so enmime is exercised against real, deliberately weird messages, which is a far better test corpus than a set of hand written fixtures.
The scope is MIME rather than mail. There is no mailbox, no folder, no message store and no search index. The library produces and consumes the encoded form of a message, and the topics on the repository say the same thing: decoder, encoder, parser, mail, mime.
The module path is versioned in the usual Go way, github.com/jhillyerd/enmime/v2, and the go.mod requires Go 1.25.0. The recent releases are v2.5.0 from 2026-09-05, v2.4.1 from 2026-06-06 and v2.4.0 from 2026-05-16, and the last push was on 2026-10-19.
The file names in the root are the API map
The repository is a single Go package with its files at the top level, and the names describe the operations without needing the documentation.
builder.go and builder_test.go are the fluent interface builder for generating MIME encoded messages, and the readme points at the wiki for builder usage examples. encoder_options.go and options.go hold the options for the encoding side. encode.go is the encoder itself and header.go is the header handling, which is where the character set and encoding transfer details live.
On the parsing side, parser.go is the parser, part.go models a parsed part, and detect.go handles content detection. match.go and inspect.go are the tools for working with the result after parsing, and marshaljson.go is the JSON marshalling for the parsed structures.
Then there are the two files that show what the library is actually used for. envelope.go models the SMTP envelope, with the RFC 5321 address list, the reverse path and the local part, and sender.go is the sending path that takes a built message and hands it to an SMTP server. envelope_fuzz_test.go shows that the envelope parsing is fuzz tested, which is the right call for anything that parses attacker controlled strings.
There are also directories worth noting. mediatype/ holds the media type handling. dsn/ is for delivery status notifications, the bounce messages that a mail server sends back and that a naive parser will choke on. internal/ holds the parts that are not part of the public API. cmd/ holds command line programs, and testdata/ holds the corpus of sample messages the tests run against.
And the last piece of the naming: boundary.go. MIME multipart messages are delimited by boundary strings, and that file is where the boundary handling lives, which is the single most common source of trouble in mail parsing.
BCC is deliberately not a header, and that trips people up
There is a note in the readme about BCC, and it is the clearest example of enmime choosing correctness over convenience.
BCC() and BCCAddrs() addresses are not written into the Bcc header of the message built by Build(). They are passed to the SMTP server as envelope recipients by Send() instead, per RFC 5322 section 3.6.3. An absent Bcc header is therefore expected, not an encoding bug, and the readme says to use Header() if you want one in the message itself.
This is a specification detail that has bitten a great many mail libraries. RFC 5322 section 3.6.3 covers the blind carbon copy in a message, and the convention that the Bcc header is omitted from the transmitted message while the recipients are carried in the envelope is what stops a copy of the message revealing the hidden recipients to everyone else. A library that helpfully wrote the Bcc header into the message would leak those addresses on every send.
So the sequence for a builder is: add the blind recipients with BCC or BCCAddrs, and when Send() runs they go into the envelope rather than the headers. If you want a visible Bcc header in the message itself, you construct one with Header().
The other place this shows up is the SMTP path. envelope.go exists because the envelope and the headers are different objects with different rules, and sender.go is what connects them. A caller who builds a message and hands it to their own SMTP client rather than using Send() has to carry the envelope recipients across themselves, and that is exactly the mistake the library's design is steering people away from.
The dependency list shows where the hard problems are
The go.mod is small and every direct dependency corresponds to a specific problem in mail handling.
github.com/gogs/chardet is the character set detection library. MIME declares a charset in a header, and mail in the wild uses a lot of charsets that are wrong, missing or lying, so detecting the actual encoding of the bytes is a separate problem from decoding them.
github.com/cention-sany/utf7 is there because UTF-7 is still a thing. It was a MIME charset for text, it was deprecated by the IETF, and it is still the content encoding on some old mail, so a parser that claims to handle real mail has to decode it.
github.com/inbucket/html2text is the HTML to text converter, used when a message is HTML only and a plain text alternative is needed.
golang.org/x/text at 0.37.0 is the Unicode library, which is what the charset handling is built on top of. github.com/pkg/errors is the error wrapping library, and github.com/stretchr/testify is the test assertions.
The indirect list is mostly test and tooling dependencies, and it includes one item worth reading twice: github.com/ssor/bom, the byte order mark reader. A BOM in the middle of a mail body, or at the start of a part, is a classic way to break a parser, and having a dedicated library for it in the tree is a good sign about the kind of input the authors expected.
The module path is github.com/jhillyerd/enmime/v2, and the go.mod declares Go 1.25.0, so the project tracks current Go releases rather than an old compatibility floor.
Building and testing it, with the race detector on
The Makefile is a Go project Makefile and it does the usual things, with a couple of choices worth noting.
The test target runs the suite with the race detector enabled.
test:
go test -race ./...For a library that parses untrusted input concurrently, the race detector is not a formality, and the presence of envelope_fuzz_test.go means the fuzzing targets exist too.
The lint target is more thorough than the usual gofmt check. It first fails on formatting differences, printing the offending files, and then runs go vet across all packages.
lint:
@test -z "$(shell gofmt -l . | tee /dev/stderr)" || echo "[WARN] Fix formatting issues with 'make fmt'"The default target runs clean, test, lint and build in sequence, so a bare make invocation is the full check. There is a fmt target using gofmt over the source files, a simplify target using gofmt -s, and a reflex target that watches for Go file changes and reruns the tests, which is the development loop the author used.
The build target is plain go build, the deps target is go get ./..., and the clean target is go clean across the package list. The source file list is computed with a find that excludes the vendor directory, and the package list comes from go list with the same exclusion, so both a vendored checkout and a module aware one work.
The repository also has a .golangci.yml for the linter configuration, a flake.nix and flake.lock with a shell.nix for a reproducible development environment, a .envrc for direnv, and a CONTRIBUTING.md that the readme points to for more information about the development status.
Where enmime is the wrong tool
There are three cases where this is the wrong library, and one where it is clearly the right one.
The first wrong case is a mail client. enmime handles the encoding and the structure of a message. It does not hold mailboxes, it does not index messages, it does not implement IMAP, and it does not do threading or search. If you need those, you need a mail server or a mail client library, and enmime is not it.
The second is a project that only sends simple messages and wants no MIME complexity at all. The standard library's net/smtp and a template for the body cover the common case of a transactional notification, and pulling in a full MIME library for that is unnecessary weight.
The third is a project that must handle the full range of modern mail formats. enmime is about MIME, and the modern additions to email, such as S/MIME signatures, are not mentioned in the readme at all. If signed and encrypted mail is a requirement, that is a different problem, and the readme is silent on it, which means you should verify before committing rather than assume.
The right case is a service that has to deal with whatever arrives. A ticketing system that accepts replies by email, a bug tracker that ingests reports, a service that both builds outgoing messages with attachments and parses inbound ones. In that situation the hard requirements are the ones enmime was built for: a tolerant parse of mail produced by unknown clients, correct encoding on the way out, and the details like the BOM handling and the charset detection taken care of.
On the alternative, the difference in approach against the standard library's mime package is tolerance. The standard library parses according to the specification and stops when the specification is violated, which is correct behaviour for a library that must not accept malformed input silently. enmime is deliberately more forgiving on the way in, because in a mail server the malformed input is the normal case rather than the exception.
That same difference is the risk. A parser that is tolerant of broken input will sometimes accept something and interpret it differently from the sender's intent, and the difference between a strict parser and a lenient one shows up as a rendering bug in a client rather than as an error you can see. enmime's own development status note is the right calibration on this: production quality, but you may still encounter messages it cannot parse. The library reduces the failure rate, it does not remove it, and the mitigation is the same as it has always been for mail: log what you failed to parse, and keep a real mailbox around to test against.
The licence is MIT, which is the permissive end and asks only for attribution, and the library is being developed in tandem with Inbucket, so both are maintained by the same people and the same release rhythm applies to each.
Editorial conclusion
enmime suits a Go service that has to build outgoing mail, parse mail that arrived from anywhere, or both, and it does not suit a project that needs a full mail client, since it handles encoding and structure rather than mailbox storage, search or IMAP. Before you adopt it, send real messages through it and check the results against what your recipients see, and remember that the BCC behaviour is deliberate rather than a bug, so verify the envelope recipients on the sending path rather than looking for a Bcc header.
Frequently asked questions
What is the enmime Go library?
enmime is a MIME encoding and decoding library for Go, focused on generating and parsing MIME encoded emails. It is being developed in tandem with the Inbucket email service and is released under the MIT License.
Why does enmime not write a Bcc header?
BCC() and BCCAddrs() addresses are passed to the SMTP server as envelope recipients by Send() rather than written into the Bcc header, per RFC 5322 section 3.6.3. An absent Bcc header is expected rather than an encoding bug, and you can use Header() if you want one in the message itself.
How do I run the enmime test suite?
The Makefile has a test target that runs go test with the race detector across all packages, and there is also a reflex target that watches for Go file changes and reruns the tests. The default make target runs clean, test, lint and build in sequence.
What does enmime do when it meets a malformed message?
The readme says enmime is production quality but notes there are many buggy MIME encoders in the wild, so you may still encounter messages it cannot parse. Its dependencies include a charset detection library, a UTF-7 decoder and a byte order mark reader, which are the usual sources of malformed input.
What Go version does enmime require?
The go.mod declares Go 1.25.0, and the module path is github.com/jhillyerd/enmime/v2. The recent releases are v2.5.0 from 2026-09-05, v2.4.1 from 2026-06-06 and v2.4.0 from 2026-05-16.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jhillyerd-enmime)Was this useful?