enmime
repository·main·Indexed 19 days ago
https://github.com/jhillyerd/enmimeA Go-based MIME encoding and decoding library focused on the generation and parsing of MIME-encoded emails. It provides a fluent MailBuilder interface for constructing complex MIME messages, an Envelope type for high-level access to email content, and utilities like mime-dump for debugging and mime-extractor for extracting attachments.
What's inside enmime
- enmime is a Go library designed for MIME encoding and decoding, specifically optimized for generating and parsing MIME-encoded emails. It is developed alongside the Inbucket email service and provides a fluent interface builder for constructing complex MIME messages.
Understand the mime-dump output format
mainWhen running
mime-dump, the output is a Markdown document organized into the following sections:- Envelope: Contains headers like
From,To, andSubject. - Body Text: The plain text content of the email.
- Body HTML: The HTML content of the email.
- Attachment List: A list of files attached to the email.
- MIME Part Tree: A visual representation of the MIME hierarchy (e.g.,
multipart/alternative,text/plain,image/png) including dispositions and filenames.
Envelope -------- From: James Hillyerd <james@makita.skynet> To: greg@nobody.com Subject: MIME test 1 Body Text --------- Test of text section Body HTML --------- Test of HTML section Attachment List --------------- MIME Part Tree -------------- multipart/alternative |-- text/plain `-- multipart/related |-- text/html `-- image/png, disposition: inline, filename: "favicon.png"- Envelope: Contains headers like
Use the mime-dump utility for debugging
mainThe
mime-dumputility is used to debugenmimeparsing by converting an email file into a human-readable Markdown document. This document describes the email's envelope, body text, body HTML, attachments, and the MIME part tree structure.# Build the utility go build # Run it against an email file ./mime-dump ../test-data/mail/html-mime-inline.rawConfigure error recovery with ReadPartErrorPolicy
mainThe
ReadPartErrorPolicytype allows you to define how the parser should behave when an error occurs while reading aPart's content. The policy function receives the*Partand theerror, returning aboolthat indicates whether the parser should attempt to recover (e.g., by using partial content).// Example of a policy that recovers from corrupt base64 in text parts policy := enmime.AllowCorruptTextPartErrorPolicy // Apply it during parser creation parser := enmime.NewParser(enmime.WithReadPartErrorPolicy(policy))How MailBuilder constructs the MIME tree
mainWhen
Build()is called,MailBuilderautomatically determines the most efficient MIME structure based on the provided content. It builds a tree ofPartstructs following this hierarchy:multipart/mixed(Root)multipart/relatedmultipart/alternativetext/plaintext/html
- Other parts (e.g., images with
Content-ID) - Inlines
- Attachments
If only
text/plainortext/htmlis provided, the tree is simplified. If both are provided, they are wrapped in amultipart/alternativecontainer.Use the Sender interface to send emails
mainThe
Senderinterface defines the contract for sending MIME messages. Implementing this interface allows you to swap out different delivery mechanisms (like SMTP, SendGrid, or a mock sender for testing) while keeping your message construction logic decoupled from the transport layer.To send a message, call
Sendwith:reversePath: The email address used for delivery error reporting (theMAIL FROMcommand in SMTP).recipients: A slice of strings containing the destination email addresses.msg: The raw byte slice of the MIME message (including headers likeFrom,To, andSubject).
Note on BCC: To send a BCC message, include the recipient's address in the
recipientsslice but omit it from themsgheaders.// Example of the Sender interface signature type Sender interface { Send(reversePath string, recipients []string, msg []byte) error }How MIME encoding is determined for a Part
mainWhen calling
Encode, the library automatically selects theContent-Transfer-Encoding(CTE) using the following logic:- Message Types: If the
ContentTypestarts withmessage/, it uses8bit(per RFC 1341). - Text Content: For non-message types that are identified as text, it scans the content. If the content contains mostly ASCII, it uses
quoted-printable. If the density of non-ASCII characters exceeds a threshold (20%), it switches tobase64. - Binary/Other: Defaults to
base64for non-text content. - Raw: If the part was parsed with
rawContentenabled, it usesteRaw(no encoding).
This logic ensures that the resulting MIME message is as efficient as possible while remaining compliant with RFC standards.
- Message Types: If the
Use MailBuilder to construct MIME messages
mainThe
MailBuilderprovides a fluent, immutable-style interface for constructing complex MIME messages. Each manipulation method returns a copy of theMailBuilder, allowing you to chain calls. This design makes the builder thread-safe for reuse if the underlying data is not modified externally.To use it, start with
enmime.Builder(), chain your configuration methods (likeFrom,To,Subject,Text,HTML, etc.), and finally callBuild()to generate a*Parttree orSend()to transmit the message.package main import ( "github.com/jhillyerd/enmime/v2" ) func main() { builder := enmime.Builder(). From("Sender Name", "sender@example.com"). To("Recipient", "recipient@example.com"). Subject("Hello World"). Text([]byte("This is the plain text body")). HTML([]byte("<h1>This is the HTML body</h1>")) part, err := builder.Build() if err != nil { panic(err) } // Use the resulting *Part tree... }Parse an email message into an Envelope
mainTo parse a MIME email message, use
ReadEnvelope(r io.Reader). This function reads the content from the provided reader and returns an*Envelope.An
Envelopeis a simplified wrapper that automatically:- Downconverts HTML to plain text if no
text/plainpart is present (unless configured otherwise). - Sorts parts into
Attachments,Inlines, andOtherPartsbased on theirContent-Disposition. - Collects parsing errors from all nested parts into the
Errorsslice.
If you need to use specific parser configurations, use the
Parser.ReadEnvelope(r io.Reader)method instead of the package-levelReadEnvelope.import "github.com/jhillyerd/enmime/v2" // ... // r is an io.Reader containing the raw MIME message envelope, err := enmime.ReadEnvelope(r) if err != nil { // handle error } // Access content fmt.Println(envelope.Text) fmt.Println(envelope.HTML) for _, attachment := range envelope.Attachments { fmt.Println("Attachment found:", attachment.Header.Get("Content-Disposition")) }- Downconverts HTML to plain text if no
Handle errors in MailBuilder
mainThe
MailBuildercaptures errors during the construction process (specifically during file I/O inAddFileAttachment,AddFileInline, orAddFileOtherPart).If an error occurs during a file operation, subsequent builder calls will be ignored, and the error will be stored. You must check for this error using the
Error()method before callingBuild()orSend().builder := enmime.Builder().AddFileAttachment("missing.txt") if err := builder.Error(); err != nil { // Handle the error }Use AllowCorruptTextPartErrorPolicy to recover text parts
mainThe
AllowCorruptTextPartErrorPolicyis a built-in error policy designed to recover partial content when encountering abase64.CorruptInputError, specifically when the part'sContentTypeistext/plainortext/html. This is useful for handling slightly malformed text-based MIME parts without failing the entire parsing process.// Use this policy to allow the parser to continue even if text parts have base64 corruption parser := enmime.NewParser(enmime.WithReadPartErrorPolicy(enmime.AllowCorruptTextPartErrorPolicy))Determine if a part contains text content
mainTheTextContent() boolmethod returnstrueif the part'sContentTypeindicates it is text-based (e.g., starts withtext/or is amultipart/type). This is used to determine appropriate content transfer encoding schemes. IfContentTypeis empty, it is treated astext/plain; charset=us-asciiper RFC 2045.