Help us shape the future of Go! Take the 2026 Go Developer Survey

mailparser

package module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: Apache-2.0, MIT Imports: 20 Imported by: 0

README

mailparser

CI Go Reference Go License

Fast and robust e-mail parsing library for Go.

[!NOTE] A Go port of stalwartlabs/mail-parser by Stalwart Labs. Known deviations are documented in DEVIATIONS.md.

Quick start

package main

import (
	"fmt"

	"github.com/yokohola/mailparser"
)

func main() {
	raw := []byte("From: Art Vandelay <art@vandelay.com>\n" +
		"Subject: Why not both?\n\nHello, world!\n")

	m := mailparser.NewMessageParser().Parse(raw)
	if m == nil {
		return // not a message
	}
	
	if subject, ok := m.Subject(); ok {
		fmt.Println("Subject:", subject)
	}
	
	if from := m.From(); from != nil {
		fmt.Println("From:", from.First().Address) // From() != nil guarantees a usable address
	}
	
	if body, ok := m.BodyText(0); ok {
		fmt.Println(body)
	}
}

Selective parsing for hot paths - parse only the headers you need:

parser := mailparser.NewMessageParser().
	WithMinimalHeaders().          // MIME + Date/Subject/From/Reply-To/To/Cc/Bcc
	IgnoreHeader(mailparser.HeaderReceived)

headersOnly := parser.ParseHeaders(raw)

See example_test.go and the mailbox package for more, including custom per-header parser configuration.

Testing & Benchmarks

go test ./...                                  # unit tests
go test -tags fuzz -fuzz '^FuzzParse$' .       # fuzzing
cd bench && go test -bench=. -count=5 .        # benchmarks vs net/mail, enmime, go-message

Benchmark numbers: bench/README.md.

License

Licensed under either of Apache-2.0 or MIT, at your option.

Derivative work of stalwartlabs/mail-parser, Copyright © 2020 Stalwart Labs LLC.

Documentation

Overview

Package mailparser is a fast and robust e-mail parsing library.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	HeaderSubject                  = HeaderName{/* contains filtered or unexported fields */}
	HeaderFrom                     = HeaderName{/* contains filtered or unexported fields */}
	HeaderTo                       = HeaderName{/* contains filtered or unexported fields */}
	HeaderCc                       = HeaderName{/* contains filtered or unexported fields */}
	HeaderDate                     = HeaderName{/* contains filtered or unexported fields */}
	HeaderBcc                      = HeaderName{/* contains filtered or unexported fields */}
	HeaderReplyTo                  = HeaderName{/* contains filtered or unexported fields */}
	HeaderSender                   = HeaderName{/* contains filtered or unexported fields */}
	HeaderComments                 = HeaderName{/* contains filtered or unexported fields */}
	HeaderInReplyTo                = HeaderName{/* contains filtered or unexported fields */}
	HeaderKeywords                 = HeaderName{/* contains filtered or unexported fields */}
	HeaderReceived                 = HeaderName{/* contains filtered or unexported fields */}
	HeaderMessageID                = HeaderName{/* contains filtered or unexported fields */}
	HeaderReferences               = HeaderName{/* contains filtered or unexported fields */}
	HeaderReturnPath               = HeaderName{/* contains filtered or unexported fields */}
	HeaderMimeVersion              = HeaderName{/* contains filtered or unexported fields */}
	HeaderContentDescription       = HeaderName{/* contains filtered or unexported fields */}
	HeaderContentID                = HeaderName{/* contains filtered or unexported fields */}
	HeaderContentLanguage          = HeaderName{/* contains filtered or unexported fields */}
	HeaderContentLocation          = HeaderName{/* contains filtered or unexported fields */}
	HeaderContentTransferEncoding  = HeaderName{/* contains filtered or unexported fields */}
	HeaderContentType              = HeaderName{/* contains filtered or unexported fields */}
	HeaderContentDisposition       = HeaderName{/* contains filtered or unexported fields */}
	HeaderResentTo                 = HeaderName{/* contains filtered or unexported fields */}
	HeaderResentFrom               = HeaderName{/* contains filtered or unexported fields */}
	HeaderResentBcc                = HeaderName{/* contains filtered or unexported fields */}
	HeaderResentCc                 = HeaderName{/* contains filtered or unexported fields */}
	HeaderResentSender             = HeaderName{/* contains filtered or unexported fields */}
	HeaderResentDate               = HeaderName{/* contains filtered or unexported fields */}
	HeaderResentMessageID          = HeaderName{/* contains filtered or unexported fields */}
	HeaderListArchive              = HeaderName{/* contains filtered or unexported fields */}
	HeaderListHelp                 = HeaderName{/* contains filtered or unexported fields */}
	HeaderListID                   = HeaderName{/* contains filtered or unexported fields */}
	HeaderListOwner                = HeaderName{/* contains filtered or unexported fields */}
	HeaderListPost                 = HeaderName{/* contains filtered or unexported fields */}
	HeaderListSubscribe            = HeaderName{/* contains filtered or unexported fields */}
	HeaderListUnsubscribe          = HeaderName{/* contains filtered or unexported fields */}
	HeaderArcAuthenticationResults = HeaderName{/* contains filtered or unexported fields */}
	HeaderArcMessageSignature      = HeaderName{/* contains filtered or unexported fields */}
	HeaderArcSeal                  = HeaderName{/* contains filtered or unexported fields */}
	HeaderDkimSignature            = HeaderName{/* contains filtered or unexported fields */}
	HeaderDkim2Signature           = HeaderName{/* contains filtered or unexported fields */}
	HeaderMessageInstance          = HeaderName{/* contains filtered or unexported fields */}
)

Known header names.

View Source
var ErrJSONDecodingUnsupported = errors.New("mailparser: JSON decoding is not supported; store the raw message and re-parse")

ErrJSONDecodingUnsupported is returned by every exported UnmarshalJSON on this package's types. A Message carries offsets, raw bytes and gated header storage that the wire form does not preserve, so decoding one back yields a Message the parser never produces. Keep the raw message and re-parse it.

View Source
var ErrWrongKind = errors.New("mailparser: header value has a different kind")

ErrWrongKind is returned by the Unwrap methods when the header value holds a different kind, or when the kind is set but its payload pointer is not.

Functions

func AddHTMLToken

func AddHTMLToken(result *strings.Builder, token []byte, addSpace bool)

AddHTMLToken appends token to result, optionally preceded by a space. A token of the form "&name;", "&#nnn;" or "&#xhh;" is decoded as an HTML entity; unknown or malformed entities are appended verbatim.

func Base64Decode

func Base64Decode(bytes []byte) ([]byte, bool)

Base64Decode decodes a base64 encoded value, ignoring ASCII whitespace. It reports false on invalid input.

func Base64DecodeStream

func Base64DecodeStream(stream iter.Seq[byte], streamLen int, stopChar byte) ([]byte, bool)

Base64DecodeStream decodes a base64 encoded value from a byte sequence, stopping successfully when stopChar is found outside of the base64 alphabet.

func DecodeHex

func DecodeHex(src []byte) (bool, []byte)

DecodeHex decodes a %XX percent-encoded byte string (RFC2231 extended parameter values), reporting success and the decoded bytes.

func HTMLToText

func HTMLToText(input string) string

HTMLToText extracts the readable text of an HTML document. It drops tags, comments and head/style/script/template contents, decodes entities, collapses whitespace runs, and turns <br> and closing </p> into newlines.

func ParseAddressDetailPart

func ParseAddressDetailPart(addr string) (string, bool)

ParseAddressDetailPart returns the text between the last '+' before the '@' of an e-mail address and the '@' itself (possibly empty), reporting false when there is no such '+'.

func ParseAddressDomain

func ParseAddressDomain(addr string) (string, bool)

ParseAddressDomain returns the text after the first '@' of an e-mail address, reporting false when either side of the '@' is empty.

func ParseAddressLocalPart

func ParseAddressLocalPart(addr string) (string, bool)

ParseAddressLocalPart returns the text before the first '@' of an e-mail address, reporting false when the address has no non-empty local part followed by a non-empty remainder.

func ParseAddressUserPart

func ParseAddressUserPart(addr string) (string, bool)

ParseAddressUserPart returns the text before the first '+' of an e-mail address when that '+' precedes the '@', and otherwise the local part.

func PreviewHTML

func PreviewHTML(html string, maxLen int) string

PreviewHTML converts HTML to text and truncates it to at most maxLen bytes, appending "..." when maxLen > 6.

func PreviewText

func PreviewText(text string, maxLen int) string

PreviewText truncates text at a UTF-8 character boundary so the result is at most maxLen bytes, appending "..." when maxLen > 6 (the dots count toward the limit).

func QuotedPrintableDecode

func QuotedPrintableDecode(bytes []byte) ([]byte, bool)

QuotedPrintableDecode decodes a quoted-printable encoded value, reporting false on invalid input.

func QuotedPrintableDecodeChar

func QuotedPrintableDecodeChar(hex1, hex2 byte) (byte, bool)

QuotedPrintableDecodeChar decodes a single =XX escape from its two hex digit characters, reporting false when either digit is invalid.

func TextToHTML

func TextToHTML(input string) string

TextToHTML wraps plain text in a minimal HTML document, converting newlines to <br/> and escaping '<'.

func ThreadName

func ThreadName(text string) string

ThreadName returns the base thread name of a Subject value, stripping reply/forward prefixes, leading mailing-list "[...]" blobs, "[fwd: ...]" wrappers and trailing "(fwd)" suffixes. The result is a substring of text.

func TrimTrailingFwd

func TrimTrailingFwd(text string) string

TrimTrailingFwd trims leading and trailing whitespace and any number of trailing "(fwd)"-style groups from text, returning a substring. It scans in reverse, and a stripped group re-arms the trailing trim.

func TruncateHTML

func TruncateHTML(html string, maxLen int) string

TruncateHTML truncates HTML markup to at most maxLen bytes, appending "..." when maxLen > 6. A cut that lands inside a tag or comment rolls back to the end of the last complete tag.

func TruncateText

func TruncateText(text string, maxLen int) string

TruncateText truncates text to at most maxLen bytes at a UTF-8 character boundary. It is an alias of PreviewText.

Types

type Addr

type Addr struct {
	// Name is the address display name including comments; "" when absent.
	Name string
	// Address is an e-mail address (RFC5322/RFC2369) or URL (RFC2369);
	// "" when absent.
	Address string
}

Addr is a single RFC5322 or RFC2369 internet address.

Both fields are plain strings and "" means absent, so absent and present-but-empty are one state and callers need no nil check. The wire form maps "" to null and back.

func (Addr) MarshalJSON

func (a Addr) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form, encoding an absent name or address as null.

func (*Addr) UnmarshalJSON

func (a *Addr) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Address

type Address struct {
	// contains filtered or unexported fields
}

Address is an RFC5322 address header value: either a flat address list or a list of named groups.

func (*Address) All

func (a *Address) All() iter.Seq[*Addr]

All iterates over every address: the list elements, or all groups' addresses flattened in order.

func (*Address) AsGroup

func (a *Address) AsGroup() ([]Group, bool)

AsGroup returns the groups; ok is false when the address holds a flat list.

func (*Address) AsList

func (a *Address) AsList() ([]Addr, bool)

AsList returns the flat list of addresses; ok is false when the address holds groups.

func (*Address) Contains

func (a *Address) Contains(addr string) bool

Contains reports whether any address field equals addr ASCII-case-insensitively. Display names are ignored, and an Addr with no Address never matches.

func (*Address) First

func (a *Address) First() *Addr

First returns the first address in the list, or the first address across the groups, skipping empty groups. It is never nil on a value a Message address accessor returns; other values can hold nothing to return.

func (*Address) IsGroup

func (a *Address) IsGroup() bool

IsGroup reports whether this address value holds groups.

func (*Address) Last

func (a *Address) Last() *Addr

Last returns the last address in the list, or the last address across the groups, skipping empty groups. It is never nil on a value a Message address accessor returns; other values can hold nothing to return.

func (Address) MarshalJSON

func (a Address) MarshalJSON() ([]byte, error)

MarshalJSON serializes the externally tagged Address variants: {"List": [...]} or {"Group": [...]}.

func (*Address) ToGroups

func (a *Address) ToGroups() []Group

ToGroups converts the address into groups. A flat list becomes one singleton Group PER address, each with an empty name, not a single group holding all of them.

func (*Address) ToList

func (a *Address) ToList() []Addr

ToList converts the address into a flat list of Addr, flattening all groups' addresses in order.

func (*Address) UnmarshalJSON

func (a *Address) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Attribute

type Attribute struct {
	Name  string
	Value string
}

Attribute is a single Content-Type/Content-Disposition attribute.

func (Attribute) MarshalJSON

func (a Attribute) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form.

func (*Attribute) UnmarshalJSON

func (a *Attribute) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type ContentType

type ContentType struct {
	Type    string
	Subtype *string // nil when absent
	// Attributes is nil when the header has no attributes; it serializes to
	// JSON null.
	Attributes []Attribute
}

ContentType is an RFC2047 Content-Type or RFC2183 Content-Disposition header value.

func (*ContentType) Attribute

func (c *ContentType) Attribute(name string) (string, bool)

Attribute returns the value of the first attribute with the given name. The match is case-SENSITIVE: the parser already lowercases attribute names.

func (*ContentType) HasAttribute

func (c *ContentType) HasAttribute(name string) bool

HasAttribute reports whether an attribute with the given name is present (case-sensitive).

func (*ContentType) IsAttachment

func (c *ContentType) IsAttachment() bool

IsAttachment reports whether the Content-Disposition type is "attachment", ASCII-case-insensitively.

func (*ContentType) IsInline

func (c *ContentType) IsInline() bool

IsInline reports whether the Content-Disposition type is "inline", ASCII-case-insensitively.

func (ContentType) MarshalJSON

func (c ContentType) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form.

func (*ContentType) RemoveAttribute

func (c *ContentType) RemoveAttribute(name string) (string, bool)

RemoveAttribute removes the first attribute with the given name (case-sensitive) and returns its value. The last attribute moves into the freed slot, so order is not preserved.

func (*ContentType) UnmarshalJSON

func (c *ContentType) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type DateTime

type DateTime struct {
	Year        uint16
	Month       uint8
	Day         uint8
	Hour        uint8
	Minute      uint8
	Second      uint8
	TZBeforeGMT bool
	TZHour      uint8
	TZMinute    uint8
}

DateTime is an RFC5322 date and time. Malformed input is preserved verbatim, so fields may hold out-of-range values (e.g. Second == 232) and the narrow unsigned widths wrap on overflow.

func DateTimeFromTimestamp

func DateTimeFromTimestamp(timestamp int64) DateTime

DateTimeFromTimestamp builds a DateTime from a Unix timestamp. The result is always UTC: TZBeforeGMT false, zero offset.

func ParseRFC822

func ParseRFC822(value string) *DateTime

ParseRFC822 parses an RFC822 date, or returns nil if it does not parse.

func ParseRFC3339

func ParseRFC3339(value string) *DateTime

ParseRFC3339 parses an RFC3339 date, or returns nil if it does not parse.

func (DateTime) Compare

func (d DateTime) Compare(other DateTime) int

Compare returns -1, 0 or 1 as d is before, equal to, or after other by timestamp.

func (DateTime) DayOfWeek

func (d DateTime) DayOfWeek() uint8

DayOfWeek returns the day of week where [0, 6] represents [Sun, Sat].

func (DateTime) IsValid

func (d DateTime) IsValid() bool

IsValid returns true if the date is valid.

func (DateTime) JulianDay

func (d DateTime) JulianDay() int64

JulianDay returns the julian day.

func (DateTime) MarshalJSON

func (d DateTime) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form.

func (DateTime) String

func (d DateTime) String() string

String returns the RFC3339 representation.

func (DateTime) ToRFC822

func (d DateTime) ToRFC822() string

ToRFC822 returns an RFC822 representation of the date.

func (DateTime) ToRFC3339

func (d DateTime) ToRFC3339() string

ToRFC3339 returns an RFC3339 representation of the parsed RFC5322 datetime field.

func (DateTime) ToTimestamp

func (d DateTime) ToTimestamp() int64

ToTimestamp returns the number of seconds since 1970-01-01T00:00:00Z (Unix epoch).

func (DateTime) ToTimestampLocal

func (d DateTime) ToTimestampLocal() int64

ToTimestampLocal returns the number of seconds since 1970-01-01T00:00:00Z (Unix epoch) in local time.

func (DateTime) ToTimezone

func (d DateTime) ToTimezone(tz int64) DateTime

ToTimezone converts the DateTime to the given timezone (signed seconds offset from GMT).

func (*DateTime) UnmarshalJSON

func (d *DateTime) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type DecoderFnc

type DecoderFnc func(bytes []byte) string

DecoderFnc decodes bytes in a legacy character set into a UTF-8 string.

func CharsetDecoder

func CharsetDecoder(charset []byte) (DecoderFnc, bool)

CharsetDecoder returns the decoder function for a character set name. Names match ASCII case-insensitively with '-' treated as '_', and only the first 45 bytes are significant.

type Encoding

type Encoding uint8

Encoding is a MIME part transfer encoding.

const (
	EncodingNone            Encoding = 0
	EncodingQuotedPrintable Encoding = 1
	EncodingBase64          Encoding = 2
)

func EncodingFrom

func EncodingFrom(v uint8) Encoding

EncodingFrom converts a raw byte: 1 and 2 map to their encodings, anything else to EncodingNone.

func (Encoding) MarshalJSON

func (e Encoding) MarshalJSON() ([]byte, error)

MarshalJSON writes the encoding as its name string. MessagePart.Encoding is skipped, so this only matters when the type is used standalone.

func (*Encoding) UnmarshalJSON

func (e *Encoding) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Greeting

type Greeting uint8

Greeting is the SMTP greeting command recorded in a Received header.

const (
	GreetingHelo Greeting = iota
	GreetingEhlo
	GreetingLhlo
)

func (Greeting) AsStr

func (g Greeting) AsStr() string

AsStr returns the display name of the greeting.

func (Greeting) MarshalJSON

func (g Greeting) MarshalJSON() ([]byte, error)

MarshalJSON serializes the bare variant identifier string.

func (Greeting) String

func (g Greeting) String() string

String is the same as AsStr.

func (*Greeting) UnmarshalJSON

func (g *Greeting) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Group

type Group struct {
	// Name is the group display name; "" when absent.
	Name string
	// Addresses are the members of the group.
	Addresses []Addr
}

Group is an RFC5322 address group.

A Group travels by value, never as a pointer, so callers never nil-check one. An unnamed group has Name == "".

func (Group) MarshalJSON

func (g Group) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form, encoding an unnamed group as null.

func (*Group) UnmarshalJSON

func (g *Group) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Header struct {
	Name  HeaderName
	Value HeaderValue
	// OffsetField is the offset of the header name in the raw message;
	// OffsetStart/OffsetEnd delimit the raw value.
	OffsetField int
	OffsetStart int
	OffsetEnd   int
}

Header is a single message header field.

func (Header) MarshalJSON

func (h Header) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form.

func (*Header) UnmarshalJSON

func (h *Header) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type HeaderForm

type HeaderForm uint8

HeaderForm selects how HeaderAs re-parses a raw header value.

const (
	// HeaderFormRaw yields the raw UTF-8 text, whitespace-trimmed.
	HeaderFormRaw HeaderForm = iota
	// HeaderFormText re-parses as an unstructured text header.
	HeaderFormText
	// HeaderFormAddresses re-parses as an address header.
	HeaderFormAddresses
	// HeaderFormGroupedAddresses re-parses as an address header. Identical
	// to HeaderFormAddresses at this layer.
	HeaderFormGroupedAddresses
	// HeaderFormMessageIDs re-parses as a Message-ID/References header.
	HeaderFormMessageIDs
	// HeaderFormDate re-parses as an RFC5322 date header.
	HeaderFormDate
	// HeaderFormURLs re-parses as a list of URLs. It routes to the address
	// parser: RFC2369 List-* URLs parse as addresses.
	HeaderFormURLs
)

type HeaderID

type HeaderID uint8

HeaderID identifies a known header name.

const (
	HeaderIDSubject                  HeaderID = 0
	HeaderIDFrom                     HeaderID = 1
	HeaderIDTo                       HeaderID = 2
	HeaderIDCc                       HeaderID = 3
	HeaderIDDate                     HeaderID = 4
	HeaderIDBcc                      HeaderID = 5
	HeaderIDReplyTo                  HeaderID = 6
	HeaderIDSender                   HeaderID = 7
	HeaderIDComments                 HeaderID = 8
	HeaderIDInReplyTo                HeaderID = 9
	HeaderIDKeywords                 HeaderID = 10
	HeaderIDReceived                 HeaderID = 11
	HeaderIDMessageID                HeaderID = 12
	HeaderIDReferences               HeaderID = 13
	HeaderIDReturnPath               HeaderID = 14
	HeaderIDMimeVersion              HeaderID = 15
	HeaderIDContentDescription       HeaderID = 16
	HeaderIDContentID                HeaderID = 17
	HeaderIDContentLanguage          HeaderID = 18
	HeaderIDContentLocation          HeaderID = 19
	HeaderIDContentTransferEncoding  HeaderID = 20
	HeaderIDContentType              HeaderID = 21
	HeaderIDContentDisposition       HeaderID = 22
	HeaderIDResentTo                 HeaderID = 23
	HeaderIDResentFrom               HeaderID = 24
	HeaderIDResentBcc                HeaderID = 25
	HeaderIDResentCc                 HeaderID = 26
	HeaderIDResentSender             HeaderID = 27
	HeaderIDResentDate               HeaderID = 28
	HeaderIDResentMessageID          HeaderID = 29
	HeaderIDListArchive              HeaderID = 30
	HeaderIDListHelp                 HeaderID = 31
	HeaderIDListID                   HeaderID = 32
	HeaderIDListOwner                HeaderID = 33
	HeaderIDListPost                 HeaderID = 34
	HeaderIDListSubscribe            HeaderID = 35
	HeaderIDListUnsubscribe          HeaderID = 36
	HeaderIDOther                    HeaderID = 37
	HeaderIDArcAuthenticationResults HeaderID = 38
	HeaderIDArcMessageSignature      HeaderID = 39
	HeaderIDArcSeal                  HeaderID = 40
	HeaderIDDkimSignature            HeaderID = 41
	HeaderIDDkim2Signature           HeaderID = 42
	HeaderIDMessageInstance          HeaderID = 43
)

type HeaderName

type HeaderName struct {
	// contains filtered or unexported fields
}

HeaderName is a header field name: either one of the known names or an arbitrary ("Other") name. Other names compare ASCII case-insensitively.

func HeaderNameFrom

func HeaderNameFrom(s string) HeaderName

HeaderNameFrom converts a string into a HeaderName. Known names match ASCII-case-insensitively and any other valid name is kept verbatim as an Other name. Empty input, or a byte outside [A-Za-z0-9_-], gives Other("").

func HeaderNameFromID

func HeaderNameFromID(id HeaderID) HeaderName

HeaderNameFromID maps an id back to a name. Ids with no reverse mapping (HeaderIDOther, Dkim2Signature, MessageInstance, out of range) yield HeaderOther("").

func HeaderOther

func HeaderOther(name string) HeaderName

HeaderOther returns an arbitrary header name.

func ParseHeaderName

func ParseHeaderName(data string) (HeaderName, bool)

ParseHeaderName validates a standalone header-name string and returns the matched well-known HeaderName or an Other name holding it unchanged. It reports false on empty input or a byte outside [A-Za-z0-9_-].

func (HeaderName) Equal

func (h HeaderName) Equal(o HeaderName) bool

Equal compares two header names. Known names match only the same known name; Other names compare ASCII-case-insensitively. A known name never equals an Other name even when the text matches: Other("From") != From.

func (HeaderName) ID

func (h HeaderName) ID() HeaderID

ID returns the header's numeric id (HeaderIDOther for arbitrary names).

func (HeaderName) IsMimeHeader

func (h HeaderName) IsMimeHeader() bool

IsMimeHeader reports whether this is one of the Content-* MIME headers.

func (HeaderName) IsOther

func (h HeaderName) IsOther() bool

IsOther reports whether this is an arbitrary (non-registered) header name.

func (HeaderName) Len

func (h HeaderName) Len() int

Len returns the byte length of the display name.

func (HeaderName) MapKey

func (h HeaderName) MapKey() HeaderName

MapKey normalizes the name for use as a map key: Other names are ASCII-lowercased so lookups agree with Equal.

func (HeaderName) MarshalJSON

func (h HeaderName) MarshalJSON() ([]byte, error)

MarshalJSON writes known names as their snake_case string and Other names as {"other": name}, case preserved.

func (HeaderName) Other

func (h HeaderName) Other() string

Other returns the arbitrary header name ("" for known names).

func (HeaderName) StaticString

func (h HeaderName) StaticString() string

StaticString is like String but returns "" for Other names.

func (HeaderName) String

func (h HeaderName) String() string

String returns the display name: the canonical spelling for known headers ("Message-ID", "MIME-Version", ...), or the Other name unchanged.

func (*HeaderName) UnmarshalJSON

func (h *HeaderName) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type HeaderSlice

type HeaderSlice []Header

HeaderSlice adds lookups to a []Header such as MessagePart.Headers. It scans in reverse, so a repeated header resolves to its LAST occurrence.

func (HeaderSlice) Header

func (hs HeaderSlice) Header(name HeaderName) *Header

Header returns the last header matching name, or nil. Matching uses HeaderName.Equal (ASCII-case-insensitive for Other names).

func (HeaderSlice) HeaderValue

func (hs HeaderSlice) HeaderValue(name HeaderName) *HeaderValue

HeaderValue returns the value of the last header matching name, or nil.

type HeaderValue

type HeaderValue struct {
	// contains filtered or unexported fields
}

HeaderValue is a parsed header value. The zero value is Empty.

func (*HeaderValue) AsAddress

func (v *HeaderValue) AsAddress() *Address

AsAddress returns the Address value, or nil for any other variant.

func (*HeaderValue) AsContentType

func (v *HeaderValue) AsContentType() *ContentType

AsContentType returns the ContentType value, or nil for any other variant.

func (*HeaderValue) AsDateTime

func (v *HeaderValue) AsDateTime() *DateTime

AsDateTime returns the DateTime value, or nil for any other variant.

func (*HeaderValue) AsReceived

func (v *HeaderValue) AsReceived() *Received

AsReceived returns the Received value, or nil for any other variant.

func (*HeaderValue) AsText

func (v *HeaderValue) AsText() (string, bool)

AsText returns the text of a Text value, or the LAST element of a TextList. Last, not first: the header getters rely on it.

func (*HeaderValue) AsTextList

func (v *HeaderValue) AsTextList() ([]string, bool)

AsTextList returns the TextList value, promoting a Text value to a single-element list.

func (*HeaderValue) IsEmpty

func (v *HeaderValue) IsEmpty() bool

IsEmpty reports whether the value is the Empty variant.

func (*HeaderValue) Kind

func (v *HeaderValue) Kind() HeaderValueKind

Kind returns the value variant.

func (*HeaderValue) Len

func (v *HeaderValue) Len() int

Len is a heuristic content length. DateTime is always 24 and Received always 1. Address sums the name and address bytes of every flattened Addr; group display names do not count. A kind whose payload pointer is missing counts 0.

func (HeaderValue) MarshalJSON

func (v HeaderValue) MarshalJSON() ([]byte, error)

MarshalJSON serializes the externally tagged HeaderValue variants; the unit variant Empty is the bare string "Empty".

func (*HeaderValue) ToText

func (v *HeaderValue) ToText() (string, bool)

ToText returns the text of a Text value only. Unlike AsText, a TextList yields false.

func (*HeaderValue) UnmarshalJSON

func (v *HeaderValue) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

func (*HeaderValue) UnwrapAddress

func (v *HeaderValue) UnwrapAddress() (Address, error)

UnwrapAddress returns the Address value, or the zero Address and ErrWrongKind.

func (*HeaderValue) UnwrapContentType

func (v *HeaderValue) UnwrapContentType() (ContentType, error)

UnwrapContentType returns the ContentType value, or the zero ContentType and ErrWrongKind.

func (*HeaderValue) UnwrapDateTime

func (v *HeaderValue) UnwrapDateTime() (DateTime, error)

UnwrapDateTime returns the DateTime value, or the zero DateTime and ErrWrongKind.

func (*HeaderValue) UnwrapReceived

func (v *HeaderValue) UnwrapReceived() (Received, error)

UnwrapReceived returns the Received value, or the zero Received and ErrWrongKind.

func (*HeaderValue) UnwrapText

func (v *HeaderValue) UnwrapText() (string, error)

UnwrapText returns the Text value, or "" and ErrWrongKind.

func (*HeaderValue) UnwrapTextList

func (v *HeaderValue) UnwrapTextList() ([]string, error)

UnwrapTextList returns the TextList value, promoting a Text value to a single-element list. Any other kind gives nil and ErrWrongKind.

type HeaderValueKind

type HeaderValueKind uint8

HeaderValueKind discriminates the variants of a HeaderValue.

const (
	// HeaderValueKindEmpty is the zero value.
	HeaderValueKindEmpty HeaderValueKind = iota
	HeaderValueKindText
	HeaderValueKindTextList
	HeaderValueKindAddress
	HeaderValueKindDateTime
	HeaderValueKindContentType
	HeaderValueKindReceived
)

type Host

type Host struct {
	// contains filtered or unexported fields
}

Host is a hostname or IP address in a Received header.

func HostIP

func HostIP(ip netip.Addr) Host

HostIP returns an IP address Host.

func HostName

func HostName(name string) Host

HostName returns a hostname Host.

func (*Host) IP

func (h *Host) IP() netip.Addr

IP returns the IP address (the zero Addr for name hosts).

func (*Host) IsIP

func (h *Host) IsIP() bool

IsIP reports whether the host is an IP address.

func (Host) MarshalJSON

func (h Host) MarshalJSON() ([]byte, error)

MarshalJSON serializes the externally tagged Host variants: {"Name": "host"} or {"IpAddr": "1.2.3.4"}.

func (*Host) Name

func (h *Host) Name() string

Name returns the hostname ("" for IP hosts).

func (Host) String

func (h Host) String() string

String returns the hostname, or the IP address in canonical form.

func (*Host) UnmarshalJSON

func (h *Host) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Message

type Message struct {
	// HTMLBody, TextBody and Attachments hold indexes into Parts.
	HTMLBody    []int
	TextBody    []int
	Attachments []int

	Parts []MessagePart

	// RawMessage holds the raw message bytes this Message comes from.
	// Parts reference it by offset. It is not serialized to JSON.
	RawMessage []byte
}

Message is a parsed RFC5322/RFC822 message.

Example (Attachments)

ExampleMessage_attachments walks every attachment, descending into nested messages, and prints names rather than writing files.

package main

import (
	"fmt"

	"github.com/yokohola/mailparser"
)

func main() {
	input := []byte(`From: Art Vandelay <art@vandelay.com> (Vandelay Industries)
To: "Colleagues": "James Smythe" <james@vandelay.com>; Friends:
    jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= <john@example.com>;
Date: Sat, 20 Nov 2021 14:22:01 -0800
Subject: Why not both importing AND exporting? =?utf-8?b?4pi6?=
Content-Type: multipart/mixed; boundary="festivus";

--festivus
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: base64

PGh0bWw+PHA+SSB3YXMgdGhpbmtpbmcgYWJvdXQgcXVpdHRpbmcgdGhlICZsZHF1bztle
HBvcnRpbmcmcmRxdW87IHRvIGZvY3VzIGp1c3Qgb24gdGhlICZsZHF1bztpbXBvcnRpbm
cmcmRxdW87LDwvcD48cD5idXQgdGhlbiBJIHRob3VnaHQsIHdoeSBub3QgZG8gYm90aD8
gJiN4MjYzQTs8L3A+PC9odG1sPg==
--festivus
Content-Type: message/rfc822; name="Exporting my book about coffee tables.eml"
Content-Disposition: inline; filename="Exporting my book about coffee tables.eml"  
Content-Transfer-Encoding: 7bit

From: "Cosmo Kramer" <kramer@kramerica.com>
Subject: Exporting my book about coffee tables
Content-Type: multipart/mixed; boundary="giddyup";

--giddyup
Content-Type: text/plain; charset="utf-16"
Content-Transfer-Encoding: quoted-printable

=FF=FE=0C!5=D8"=DD5=D8)=DD5=D8-=DD =005=D8*=DD5=D8"=DD =005=D8"=
=DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD =
=005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"=
=DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00
--giddyup
Content-Type: image/gif; name*1="about "; name*0="Book ";
              name*2*=utf-8''%e2%98%95 tables.gif
Content-Transfer-Encoding: Base64
Content-Disposition: attachment

R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
--giddyup--
--festivus--
`)

	printAttachments(mailparser.NewMessageParser().Parse(input))
}

// printAttachments prints the name of every attachment it finds.
func printAttachments(message *mailparser.Message) {
	for attachment := range message.AllAttachments() {
		if !attachment.IsMessage() {
			name, ok := attachment.AttachmentName()
			if !ok {
				name = "Untitled"
			}
			fmt.Println(name)
		} else {
			printAttachments(attachment.Message())
		}
	}
}

func (*Message) AllAttachments

func (m *Message) AllAttachments() iter.Seq[*MessagePart]

AllAttachments iterates over the attachment parts, stopping early on a bad part id. Named All* because Attachments is already the index field.

func (*Message) AllBcc

func (m *Message) AllBcc() iter.Seq[*Address]

AllBcc iterates over all Bcc headers as Addresses in message order. RFC5322 section 4.5.3 allows the header to repeat. Headers carrying no usable address are skipped, so no yielded value is nil.

func (*Message) AllCc

func (m *Message) AllCc() iter.Seq[*Address]

AllCc iterates over all Cc headers as Addresses in message order. RFC5322 section 4.5.3 allows the header to repeat. Headers carrying no usable address are skipped, so no yielded value is nil.

func (*Message) AllReceived added in v0.1.5

func (m *Message) AllReceived() iter.Seq[*Received]

AllReceived iterates over all Received headers in the order they appear in the message. Headers whose value is not a usable Received are skipped, so no yielded value is nil.

func (*Message) AllTo

func (m *Message) AllTo() iter.Seq[*Address]

AllTo iterates over all To headers as Addresses in message order. RFC5322 section 4.5.3 allows the header to repeat. Headers carrying no usable address are skipped, so no yielded value is nil.

func (*Message) Attachment

func (m *Message) Attachment(pos int) *MessagePart

Attachment returns the pos-th attachment part, or nil.

func (*Message) AttachmentCount

func (m *Message) AttachmentCount() int

AttachmentCount returns the number of attachments.

func (*Message) AttachmentName

func (m *Message) AttachmentName() (string, bool)

AttachmentName returns the attachment file name, if any.

func (*Message) Bcc

func (m *Message) Bcc() *Address

Bcc returns the last Bcc header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) BodyHTML

func (m *Message) BodyHTML(pos int) (string, bool)

BodyHTML returns the pos-th HTML body part as text/html. An HTML part comes back as-is, a Text part is converted with TextToHTML, and any other part type yields false.

func (*Message) BodyPreview

func (m *Message) BodyPreview(previewLen int) (string, bool)

BodyPreview returns a preview of the message body of at most previewLen bytes. Text bodies take absolute priority: if TextBody is non-empty but its first part is unusable, the result is false without falling back to HTML.

func (*Message) BodyText

func (m *Message) BodyText(pos int) (string, bool)

BodyText returns the pos-th text body part as text/plain. A Text part comes back as-is, an HTML part is converted with HTMLToText, and any other part type yields false.

func (*Message) Cc

func (m *Message) Cc() *Address

Cc returns the last Cc header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) Comments

func (m *Message) Comments() *HeaderValue

Comments returns the last Comments value, or nil when the header is absent.

func (*Message) ContentDescription

func (m *Message) ContentDescription() (string, bool)

ContentDescription returns the root part's Content-Description field.

func (*Message) ContentDisposition

func (m *Message) ContentDisposition() *ContentType

ContentDisposition returns the root part's Content-Disposition field.

func (*Message) ContentID

func (m *Message) ContentID() (string, bool)

ContentID returns the root part's Content-ID field.

func (*Message) ContentLanguage

func (m *Message) ContentLanguage() *HeaderValue

ContentLanguage returns the root part's Content-Language value, or nil when the header is absent.

func (*Message) ContentLocation

func (m *Message) ContentLocation() (string, bool)

ContentLocation returns the root part's Content-Location field.

func (*Message) ContentTransferEncoding

func (m *Message) ContentTransferEncoding() (string, bool)

ContentTransferEncoding returns the root part's Content-Transfer-Encoding field.

func (*Message) ContentType

func (m *Message) ContentType() *ContentType

ContentType returns the root part's Content-Type field.

func (*Message) Date

func (m *Message) Date() *DateTime

Date returns the DateTime of the last Date header, or nil when the header is absent or its value is not a usable date.

func (*Message) From

func (m *Message) From() *Address

From returns the last From header as an Address, or nil when the header is absent or carries no usable address. A non-nil result needs no further check: First() and Last() resolve and every Addr in it has an Address.

func (*Message) HTMLBodies

func (m *Message) HTMLBodies() iter.Seq[*MessagePart]

HTMLBodies iterates over the HTML body parts.

func (*Message) HTMLBodyCount

func (m *Message) HTMLBodyCount() int

HTMLBodyCount returns the number of HTML body parts.

func (*Message) HTMLPart

func (m *Message) HTMLPart(pos int) *MessagePart

HTMLPart returns the pos-th inline HTML body part, or nil.

func (*Message) Header

func (m *Message) Header(name HeaderName) *HeaderValue

Header returns the value of the last header matching name, or nil.

func (*Message) HeaderAs

func (m *Message) HeaderAs(name HeaderName, form HeaderForm) []HeaderValue

HeaderAs re-parses EVERY header matching name from its raw bytes according to form, in message order. A header whose offsets fall outside RawMessage contributes an Empty value, so there is always one entry per match.

func (*Message) HeaderRaw

func (m *Message) HeaderRaw(name HeaderName) (string, bool)

HeaderRaw returns the raw text of the last header matching name. ok is false when the header is absent, its offsets fall outside RawMessage, or its raw bytes are not valid UTF-8.

func (*Message) HeaderValues

func (m *Message) HeaderValues(name HeaderName) iter.Seq[*HeaderValue]

HeaderValues iterates over the values of ALL headers matching name, in message order.

func (*Message) Headers

func (m *Message) Headers() []Header

Headers returns all root-part headers in message order.

func (*Message) HeadersRaw

func (m *Message) HeadersRaw() iter.Seq2[string, string]

HeadersRaw iterates over (display name, raw text) pairs for every header in message order. Headers whose offsets fall outside RawMessage or whose raw bytes are not valid UTF-8 are silently skipped.

func (*Message) InReplyTo

func (m *Message) InReplyTo() *HeaderValue

InReplyTo returns the last In-Reply-To value, or nil when the header is absent.

func (*Message) IsContentType

func (m *Message) IsContentType(ctype, subtype string) bool

IsContentType reports whether the Content-Type matches type and subtype.

func (*Message) IsEmpty

func (m *Message) IsEmpty() bool

IsEmpty reports whether the message has no parts. Only a hand-built value is ever empty: Parse returns nil rather than a part-less Message, so a non-nil parse result always has at least one part.

func (*Message) Keywords

func (m *Message) Keywords() *HeaderValue

Keywords returns the last Keywords value, or nil when the header is absent.

func (*Message) ListArchive

func (m *Message) ListArchive() *HeaderValue

ListArchive returns the last List-Archive value, or nil when the header is absent.

func (*Message) ListHelp

func (m *Message) ListHelp() *HeaderValue

ListHelp returns the last List-Help value, or nil when the header is absent.

func (*Message) ListID

func (m *Message) ListID() *HeaderValue

ListID returns the last List-ID value, or nil when the header is absent.

func (*Message) ListOwner

func (m *Message) ListOwner() *HeaderValue

ListOwner returns the last List-Owner value, or nil when the header is absent.

func (*Message) ListPost

func (m *Message) ListPost() *HeaderValue

ListPost returns the last List-Post value, or nil when the header is absent.

func (*Message) ListSubscribe

func (m *Message) ListSubscribe() *HeaderValue

ListSubscribe returns the last List-Subscribe value, or nil when the header is absent.

func (*Message) ListUnsubscribe

func (m *Message) ListUnsubscribe() *HeaderValue

ListUnsubscribe returns the last List-Unsubscribe value, or nil when the header is absent.

func (Message) MarshalJSON

func (m Message) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form; RawMessage is never emitted.

func (*Message) MessageID

func (m *Message) MessageID() (string, bool)

MessageID returns the text of the last Message-ID header.

func (*Message) MimeVersion

func (m *Message) MimeVersion() *HeaderValue

MimeVersion returns the last MIME-Version value, or nil when the header is absent.

func (*Message) Part

func (m *Message) Part(pos int) *MessagePart

Part returns the message part at pos, or nil.

func (*Message) Received

func (m *Message) Received() *Received

Received returns the LAST Received header, or nil when the header is absent or its value is not a usable Received.

func (*Message) References

func (m *Message) References() *HeaderValue

References returns the last References value, or nil when the header is absent.

func (*Message) RemoveHeader

func (m *Message) RemoveHeader(name HeaderName) (HeaderValue, bool)

RemoveHeader removes the FIRST header matching name and returns its value. The LAST header moves into the freed slot, so order is not preserved.

func (*Message) ReplyTo

func (m *Message) ReplyTo() *Address

ReplyTo returns the last Reply-To header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) ResentBcc

func (m *Message) ResentBcc() *Address

ResentBcc returns the last Resent-Bcc header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) ResentCc

func (m *Message) ResentCc() *Address

ResentCc returns the last Resent-Cc header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) ResentDate

func (m *Message) ResentDate() *DateTime

ResentDate returns the DateTime of the last Resent-Date header, or nil when the header is absent or its value is not a usable date.

func (*Message) ResentFrom

func (m *Message) ResentFrom() *Address

ResentFrom returns the last Resent-From header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) ResentMessageID

func (m *Message) ResentMessageID() (string, bool)

ResentMessageID returns the text of the last Resent-Message-ID header.

func (*Message) ResentSender

func (m *Message) ResentSender() *Address

ResentSender returns the last Resent-Sender header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) ResentTo

func (m *Message) ResentTo() *Address

ResentTo returns the last Resent-To header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) ReturnAddress

func (m *Message) ReturnAddress() (string, bool)

ReturnAddress returns the return address from Return-Path or From. A Text Return-Path wins, a TextList one yields its last element and never falls through. Otherwise From supplies its first address, and a From with none yields false. The parser gates From, so a stored From always has a first address; a transplanted value that does not still yields false.

func (*Message) ReturnPath

func (m *Message) ReturnPath() *HeaderValue

ReturnPath returns the last Return-Path value, or nil when the header is absent.

func (*Message) RootPart

func (m *Message) RootPart() *MessagePart

RootPart returns the root message part, or nil when the message has no parts. The parser never produces that state; a hand-built Message can.

func (*Message) RootRawMessage

func (m *Message) RootRawMessage() []byte

RootRawMessage returns RawMessage[OffsetHeader:OffsetEnd] of Parts[0], or nil when out of range. Not the RawMessage field, which is the whole input.

func (*Message) Sender

func (m *Message) Sender() *Address

Sender returns the last Sender header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) Subject

func (m *Message) Subject() (string, bool)

Subject returns the text of the last Subject header.

func (*Message) TextBodies

func (m *Message) TextBodies() iter.Seq[*MessagePart]

TextBodies iterates over the text body parts.

func (*Message) TextBodyCount

func (m *Message) TextBodyCount() int

TextBodyCount returns the number of plain text body parts.

func (*Message) TextPart

func (m *Message) TextPart(pos int) *MessagePart

TextPart returns the pos-th inline text body part, or nil.

func (*Message) ThreadName

func (m *Message) ThreadName() (string, bool)

ThreadName returns the message thread name or "base subject" as defined in RFC5256 section 2.1; ok is false when there is no usable Subject text, exactly as Subject() reports it - a present but empty Subject included.

func (*Message) To

func (m *Message) To() *Address

To returns the last To header as an Address, or nil when the header is absent or carries no usable address.

func (*Message) UnmarshalJSON

func (m *Message) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type MessageParser

type MessageParser struct {
	// contains filtered or unexported fields
}

MessageParser is a configurable RFC5322/RFC822 message parser.

With no custom parser registered it parses every IANA-registered header with that header's parser and everything else raw. Registering one custom parser replaces that dispatch with the custom map plus the default parser.

Example (Custom)

ExampleMessageParser_custom builds parsers that handle only a chosen subset of headers.

package main

import (
	"github.com/yokohola/mailparser"
)

func main() {
	input := []byte(`From: Art Vandelay <art@vandelay.com> (Vandelay Industries)
To: "Colleagues": "James Smythe" <james@vandelay.com>; Friends:
    jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= <john@example.com>;
Date: Sat, 20 Nov 2021 14:22:01 -0800
Subject: Why not both importing AND exporting? =?utf-8?b?4pi6?=
Content-Type: multipart/mixed; boundary="festivus";

--festivus
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: base64

PGh0bWw+PHA+SSB3YXMgdGhpbmtpbmcgYWJvdXQgcXVpdHRpbmcgdGhlICZsZHF1bztle
HBvcnRpbmcmcmRxdW87IHRvIGZvY3VzIGp1c3Qgb24gdGhlICZsZHF1bztpbXBvcnRpbm
cmcmRxdW87LDwvcD48cD5idXQgdGhlbiBJIHRob3VnaHQsIHdoeSBub3QgZG8gYm90aD8
gJiN4MjYzQTs8L3A+PC9odG1sPg==
--festivus
Content-Type: message/rfc822

From: "Cosmo Kramer" <kramer@kramerica.com>
Subject: Exporting my book about coffee tables
Content-Type: multipart/mixed; boundary="giddyup";

--giddyup
Content-Type: text/plain; charset="utf-16"
Content-Transfer-Encoding: quoted-printable

=FF=FE=0C!5=D8"=DD5=D8)=DD5=D8-=DD =005=D8*=DD5=D8"=DD =005=D8"=
=DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD =
=005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"=
=DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00
--giddyup
Content-Type: image/gif; name*1="about "; name*0="Book ";
              name*2*=utf-8''%e2%98%95 tables.gif
Content-Transfer-Encoding: Base64
Content-Disposition: attachment

R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
--giddyup--
--festivus--
`)

	// Parse only the message headers
	_ = mailparser.NewMessageParser().ParseHeaders(input).Headers()

	// Parse only the message body, ignoring all headers (except MIME
	// headers, which are required to parse the body)
	_ = mailparser.NewMessageParser().
		WithMimeHeaders().
		DefaultHeaderIgnore().
		Parse(input)

	// Parse only To, From, Date, and Subject headers. All other headers
	// are parsed as raw.
	_ = mailparser.NewMessageParser().
		WithMimeHeaders().
		HeaderText(mailparser.HeaderSubject).
		HeaderAddress(mailparser.HeaderFrom).
		HeaderAddress(mailparser.HeaderTo).
		HeaderDate(mailparser.HeaderDate).
		DefaultHeaderRaw().
		Parse(input)
}

func NewMessageParser

func NewMessageParser() *MessageParser

NewMessageParser creates a message parser that parses every IANA-registered header with that header's parser and every Other header as raw.

func (*MessageParser) DefaultHeaderIgnore

func (p *MessageParser) DefaultHeaderIgnore() *MessageParser

DefaultHeaderIgnore ignores and skips parsing all other headers.

func (*MessageParser) DefaultHeaderRaw

func (p *MessageParser) DefaultHeaderRaw() *MessageParser

DefaultHeaderRaw parses all other headers as raw strings, no RFC 2047 decoding is done.

func (*MessageParser) DefaultHeaderText

func (p *MessageParser) DefaultHeaderText() *MessageParser

DefaultHeaderText parses all other headers as text decoding RFC 2047 encoded words.

func (*MessageParser) HeaderAddress

func (p *MessageParser) HeaderAddress(header HeaderName) *MessageParser

HeaderAddress parses a header as an address.

func (*MessageParser) HeaderCommaSeparated

func (p *MessageParser) HeaderCommaSeparated(header HeaderName) *MessageParser

HeaderCommaSeparated parses a header as a comma-separated list of values.

func (*MessageParser) HeaderContentType

func (p *MessageParser) HeaderContentType(header HeaderName) *MessageParser

HeaderContentType parses a header as a MIME Content-Type or Content-Disposition type.

func (*MessageParser) HeaderDate

func (p *MessageParser) HeaderDate(header HeaderName) *MessageParser

HeaderDate parses a header as a RFC 5322 date.

func (*MessageParser) HeaderID

func (p *MessageParser) HeaderID(header HeaderName) *MessageParser

HeaderID parses a header as an ID.

func (*MessageParser) HeaderRaw

func (p *MessageParser) HeaderRaw(header HeaderName) *MessageParser

HeaderRaw parses a header as a raw string, no RFC 2047 decoding is done.

func (*MessageParser) HeaderReceived

func (p *MessageParser) HeaderReceived(header HeaderName) *MessageParser

HeaderReceived parses a header as a received header.

func (*MessageParser) HeaderText

func (p *MessageParser) HeaderText(header HeaderName) *MessageParser

HeaderText parses a header as text decoding RFC 2047 encoded words.

func (*MessageParser) IgnoreHeader

func (p *MessageParser) IgnoreHeader(header HeaderName) *MessageParser

IgnoreHeader ignores and skips parsing a header. The header bytes are still consumed from the stream so subsequent offsets stay correct.

func (*MessageParser) Parse

func (p *MessageParser) Parse(rawMessage []byte) *Message

Parse parses a byte slice containing an RFC5322 raw message and returns a Message. It never panics and parses on a best-effort basis. It returns nil only when the input yields neither a body part nor a header field: an input with no header line still parses when a body does ("\n" gives one empty text part), and a header block with no body parses as a headers-only message.

Example

ExampleMessageParser_Parse walks a parsed message's headers and bodies.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/yokohola/mailparser"
)

func main() {
	input := []byte(`From: Art Vandelay <art@vandelay.com> (Vandelay Industries)
To: "Colleagues": "James Smythe" <james@vandelay.com>; Friends:
    jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= <john@example.com>;
Date: Sat, 20 Nov 2021 14:22:01 -0800
Subject: Why not both importing AND exporting? =?utf-8?b?4pi6?=
Content-Type: multipart/mixed; boundary="festivus";

--festivus
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: base64

PGh0bWw+PHA+SSB3YXMgdGhpbmtpbmcgYWJvdXQgcXVpdHRpbmcgdGhlICZsZHF1bztle
HBvcnRpbmcmcmRxdW87IHRvIGZvY3VzIGp1c3Qgb24gdGhlICZsZHF1bztpbXBvcnRpbm
cmcmRxdW87LDwvcD48cD5idXQgdGhlbiBJIHRob3VnaHQsIHdoeSBub3QgZG8gYm90aD8
gJiN4MjYzQTs8L3A+PC9odG1sPg==
--festivus
Content-Type: message/rfc822

From: "Cosmo Kramer" <kramer@kramerica.com>
Subject: Exporting my book about coffee tables
Content-Type: multipart/mixed; boundary="giddyup";

--giddyup
Content-Type: text/plain; charset="utf-16"
Content-Transfer-Encoding: quoted-printable

=FF=FE=0C!5=D8"=DD5=D8)=DD5=D8-=DD =005=D8*=DD5=D8"=DD =005=D8"=
=DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD =
=005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"=
=DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00
--giddyup
Content-Type: image/gif; name*1="about "; name*0="Book ";
              name*2*=utf-8''%e2%98%95 tables.gif
Content-Transfer-Encoding: Base64
Content-Disposition: attachment

R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
--giddyup--
--festivus--
`)

	message := mailparser.NewMessageParser().Parse(input)

	// Parses addresses (including comments), lists and groups
	from := message.From().First() // well-formed input: From() is non-nil and then guarantees a usable address
	fmt.Printf("%s <%s>\n", from.Name, from.Address)

	groups, _ := message.To().AsGroup()
	for _, group := range groups {
		for _, addr := range group.Addresses {
			fmt.Printf("%s: %s\n", group.Name, addr.Address)
		}
	}

	fmt.Println(message.Date().ToRFC3339())

	// RFC2047 support for encoded text in message readers
	subject, _ := message.Subject()
	fmt.Println(subject)

	// HTML and text body parts are returned conforming to RFC8621,
	// Section 4.1.4
	bodyHTML, _ := message.BodyHTML(0)
	fmt.Println(bodyHTML)

	// HTML parts are converted to plain text (and viceversa) when missing
	bodyText, _ := message.BodyText(0)
	fmt.Println(bodyText)

	// Supports nested messages as well as multipart/digest
	nestedMessage := message.Attachment(0).Message()

	nestedSubject, _ := nestedMessage.Subject()
	fmt.Println(nestedSubject)

	// Handles UTF-* as well as many legacy encodings
	nestedText, _ := nestedMessage.BodyText(0)
	fmt.Println(nestedText)

	nestedHTML, _ := nestedMessage.BodyHTML(0)
	fmt.Println(nestedHTML)

	nestedAttachment := nestedMessage.Attachment(0)

	fmt.Println(nestedAttachment.Len())

	// Full RFC2231 support for continuations and character sets
	attachmentName, _ := nestedAttachment.AttachmentName()
	fmt.Println(attachmentName)

	// Integrates with encoding/json on the export side only. Unmarshal
	// returns ErrJSONDecodingUnsupported, so keep the raw message.
	pretty, _ := json.MarshalIndent(message, "", "  ")
	fmt.Println(string(pretty))
}

func (*MessageParser) ParseHeaders

func (p *MessageParser) ParseHeaders(rawMessage []byte) *Message

ParseHeaders parses a byte slice containing the RFC5322 raw message and returns a Message containing only the headers, or nil if no headers were parsed.

func (*MessageParser) WithAddressHeaders

func (p *MessageParser) WithAddressHeaders() *MessageParser

WithAddressHeaders registers parsers for the 11 RFC5322 address headers: From, Sender, Reply-To, To, Cc, Bcc and their Resent- forms.

func (*MessageParser) WithDateHeaders

func (p *MessageParser) WithDateHeaders() *MessageParser

WithDateHeaders registers parsers for Date and Resent-Date.

func (*MessageParser) WithMessageIDs

func (p *MessageParser) WithMessageIDs() *MessageParser

WithMessageIDs registers parsers for Message-Id, In-Reply-To, References and Resent-Message-Id.

func (*MessageParser) WithMimeHeaders

func (p *MessageParser) WithMimeHeaders() *MessageParser

WithMimeHeaders registers parsers for Content-Type, Content-Disposition, Content-Id, Content-Description, Content-Location and Content-Transfer-Encoding. Parsing message bodies requires them.

func (*MessageParser) WithMinimalHeaders

func (p *MessageParser) WithMinimalHeaders() *MessageParser

WithMinimalHeaders registers parsers for all MIME headers plus Date, Subject, From, Reply-To, To, Cc and Bcc.

func (*MessageParser) WithoutHeader

func (p *MessageParser) WithoutHeader(header HeaderName) *MessageParser

WithoutHeader removes a custom header parser.

type MessagePart

type MessagePart struct {
	Headers           []Header
	IsEncodingProblem bool
	Body              PartType
	// Encoding is the transfer encoding used by the raw body.
	// It is not serialized to JSON.
	Encoding     Encoding
	OffsetHeader int
	OffsetBody   int
	OffsetEnd    int
}

MessagePart is a single MIME part of a message.

func (*MessagePart) AttachmentName

func (p *MessagePart) AttachmentName() (string, bool)

AttachmentName returns the attachment file name, if any.

func (*MessagePart) ContentDescription

func (p *MessagePart) ContentDescription() (string, bool)

ContentDescription returns the part's Content-Description field.

func (*MessagePart) ContentDisposition

func (p *MessagePart) ContentDisposition() *ContentType

ContentDisposition returns the part's Content-Disposition field.

func (*MessagePart) ContentID

func (p *MessagePart) ContentID() (string, bool)

ContentID returns the part's Content-ID field.

func (*MessagePart) ContentLanguage

func (p *MessagePart) ContentLanguage() *HeaderValue

ContentLanguage returns the part's Content-Language value, or nil when the header is absent.

func (*MessagePart) ContentLocation

func (p *MessagePart) ContentLocation() (string, bool)

ContentLocation returns the part's Content-Location field.

func (*MessagePart) ContentTransferEncoding

func (p *MessagePart) ContentTransferEncoding() (string, bool)

ContentTransferEncoding returns the part's Content-Transfer-Encoding field.

func (*MessagePart) ContentType

func (p *MessagePart) ContentType() *ContentType

ContentType returns the part's Content-Type field.

func (*MessagePart) Contents

func (p *MessagePart) Contents() []byte

Contents returns the body as bytes: text bytes for Text/HTML, decoded bytes for binary, the nested raw message for message parts, nil for multipart. Text/HTML callers get a copy, since Go cannot alias a string's bytes.

func (*MessagePart) IsBinary

func (p *MessagePart) IsBinary() bool

IsBinary reports whether the part is binary (Binary or InlineBinary).

func (*MessagePart) IsContentType

func (p *MessagePart) IsContentType(ctype, subtype string) bool

IsContentType reports whether the Content-Type matches type and subtype.

func (*MessagePart) IsEmpty

func (p *MessagePart) IsEmpty() bool

IsEmpty reports whether the body part is empty.

func (*MessagePart) IsMessage

func (p *MessagePart) IsMessage() bool

IsMessage reports whether the part is a nested message.

func (*MessagePart) IsMultipart

func (p *MessagePart) IsMultipart() bool

IsMultipart reports whether the part is multipart.

func (*MessagePart) IsText

func (p *MessagePart) IsText() bool

IsText reports whether the part is a text/* part (Text or HTML).

func (*MessagePart) IsTextHTML

func (p *MessagePart) IsTextHTML() bool

IsTextHTML reports whether the part is a text/html part.

func (*MessagePart) Len

func (p *MessagePart) Len() int

Len returns the body content length in bytes, 0 for multipart parts.

func (MessagePart) MarshalJSON

func (p MessagePart) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form; Encoding is never emitted.

func (*MessagePart) Message

func (p *MessagePart) Message() *Message

Message returns the nested message, or nil.

func (*MessagePart) RawBodyOffset

func (p *MessagePart) RawBodyOffset() int

RawBodyOffset returns the raw body offset of this part.

func (*MessagePart) RawEndOffset

func (p *MessagePart) RawEndOffset() int

RawEndOffset returns the raw end offset of this part.

func (*MessagePart) RawHeaderOffset

func (p *MessagePart) RawHeaderOffset() int

RawHeaderOffset returns the raw header offset of this part.

func (*MessagePart) RawLen

func (p *MessagePart) RawLen() int

RawLen returns OffsetEnd - OffsetHeader, saturating at 0 so the result is never negative.

func (*MessagePart) String

func (p *MessagePart) String() string

String returns the text contents, or "[no contents]" when there are none.

func (*MessagePart) SubParts

func (p *MessagePart) SubParts() ([]int, bool)

SubParts returns the child part indexes of a multipart part; ok is false for any other part type.

func (*MessagePart) TextContents

func (p *MessagePart) TextContents() (string, bool)

TextContents returns the body as a string. Binary and message parts convert only when their bytes are valid UTF-8, with no replacement characters. Multipart parts have no contents.

func (*MessagePart) UnmarshalJSON

func (p *MessagePart) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type MimeHeaders

type MimeHeaders interface {
	// ContentDescription returns the Content-Description field.
	ContentDescription() (string, bool)
	// ContentDisposition returns the Content-Disposition field, or nil.
	ContentDisposition() *ContentType
	// ContentID returns the Content-ID field.
	ContentID() (string, bool)
	// ContentTransferEncoding returns the Content-Transfer-Encoding field.
	ContentTransferEncoding() (string, bool)
	// ContentType returns the Content-Type field, or nil.
	ContentType() *ContentType
	// ContentLanguage returns the Content-Language value, or nil when the
	// header is absent.
	ContentLanguage() *HeaderValue
	// ContentLocation returns the Content-Location field.
	ContentLocation() (string, bool)
	// AttachmentName returns the attachment file name, if any: the
	// Content-Disposition "filename" attribute, falling back to the
	// Content-Type "name" attribute.
	AttachmentName() (string, bool)
	// IsContentType reports whether the Content-Type matches the given
	// type and subtype, ASCII-case-insensitively.
	IsContentType(ctype, subtype string) bool
}

MimeHeaders gives access to the MIME header fields. Implemented by *Message (via its root part) and *MessagePart.

type PartKind

type PartKind uint8

PartKind discriminates the body variants of a PartType.

const (
	// PartKindMultipart is the zero value: an empty multipart.
	PartKindMultipart PartKind = iota
	PartKindText
	PartKindHTML
	PartKindBinary
	PartKindInlineBinary
	PartKindMessage
)

type PartType

type PartType struct {
	// contains filtered or unexported fields
}

PartType is the decoded body of a message part: text, HTML, binary, inline binary, a nested RFC5322 message, or the child part indexes of a multipart part. The zero value is an empty multipart.

func PartTypeBinary

func PartTypeBinary(b []byte) PartType

PartTypeBinary returns a binary body.

func PartTypeHTML

func PartTypeHTML(s string) PartType

PartTypeHTML returns a text/html body.

func PartTypeInlineBinary

func PartTypeInlineBinary(b []byte) PartType

PartTypeInlineBinary returns an inline binary body.

func PartTypeMessage

func PartTypeMessage(m *Message) PartType

PartTypeMessage returns a nested message body.

func PartTypeMultipart

func PartTypeMultipart(subparts []int) PartType

PartTypeMultipart returns a multipart body holding child part indexes.

func PartTypeText

func PartTypeText(s string) PartType

PartTypeText returns a text/* body.

func (*PartType) Binary

func (p *PartType) Binary() []byte

Binary returns the decoded bytes for Binary and InlineBinary parts.

func (*PartType) Kind

func (p *PartType) Kind() PartKind

Kind returns the body variant.

func (*PartType) Len

func (p *PartType) Len() int

Len returns the byte length of the body: text length for Text/HTML, byte length for the binary variants, 0 for Multipart. A Message part measures the nested message's entire RawMessage buffer, and 0 when that message is nil.

func (PartType) MarshalJSON

func (p PartType) MarshalJSON() ([]byte, error)

MarshalJSON serializes the externally tagged PartType variants: {"Text": "..."}, {"Html": "..."}, {"Binary": [n, ...]}, {"InlineBinary": [n, ...]}, {"Message": {...}} and {"Multipart": [id, ...]}.

func (*PartType) Message

func (p *PartType) Message() *Message

Message returns the nested message, or nil.

func (*PartType) SubParts

func (p *PartType) SubParts() []int

SubParts returns the child part indexes of a multipart part, or nil.

func (*PartType) Text

func (p *PartType) Text() string

Text returns the decoded text for Text and HTML parts ("" otherwise).

func (*PartType) UnmarshalJSON

func (p *PartType) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Protocol

type Protocol uint8

Protocol is an IANA mail transmission type from a Received "with" clause.

const (
	// IANA Mail Transmission Types
	ProtocolSMTP Protocol = iota
	ProtocolESMTP
	ProtocolESMTPA
	ProtocolESMTPS
	ProtocolESMTPSA
	ProtocolLMTP
	ProtocolLMTPA
	ProtocolLMTPS
	ProtocolLMTPSA
	ProtocolMMS
	ProtocolUTF8SMTP
	ProtocolUTF8SMTPA
	ProtocolUTF8SMTPS
	ProtocolUTF8SMTPSA
	ProtocolUTF8LMTP
	ProtocolUTF8LMTPA
	ProtocolUTF8LMTPS
	ProtocolUTF8LMTPSA

	// Non-Standard Mail Transmission Types
	ProtocolHTTP
	ProtocolHTTPS
	ProtocolIMAP
	ProtocolPOP3
	ProtocolLocal // includes stdin, socket, etc.
)

func (Protocol) AsStr

func (p Protocol) AsStr() string

AsStr returns the display name of the protocol. It matches the JSON name, so that table is reused.

func (Protocol) MarshalJSON

func (p Protocol) MarshalJSON() ([]byte, error)

MarshalJSON serializes the bare variant identifier string.

func (Protocol) String

func (p Protocol) String() string

String is the same as AsStr.

func (*Protocol) UnmarshalJSON

func (p *Protocol) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type Received

type Received struct {
	From       *Host
	FromIP     *netip.Addr
	FromIPRev  *string
	By         *Host
	For        *string
	With       *Protocol
	TLSVersion *TLSVersion
	TLSCipher  *string
	ID         *string
	Ident      *string
	Helo       *Host
	HeloCmd    *Greeting
	Via        *string
	Date       *DateTime
}

Received is a parsed Received header (RFC5321/RFC5322 trace field).

func (Received) MarshalJSON

func (r Received) MarshalJSON() ([]byte, error)

MarshalJSON writes the wire form.

func (*Received) UnmarshalJSON

func (r *Received) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

type TLSVersion

type TLSVersion uint8

TLSVersion is a TLS protocol version from a Received header.

const (
	SSLv2 TLSVersion = iota
	SSLv3
	TLSv1_0
	TLSv1_1
	TLSv1_2
	TLSv1_3
	DTLSv1_0
	DTLSv1_2
	DTLSv1_3
)

The constant spellings are load-bearing: the JSON form ("TLSv1_2") and the display form ("TLSv1.2") both derive from them.

func (TLSVersion) AsStr

func (t TLSVersion) AsStr() string

AsStr returns the display name of the TLS version.

func (TLSVersion) MarshalJSON

func (t TLSVersion) MarshalJSON() ([]byte, error)

MarshalJSON serializes the bare variant identifier string.

func (TLSVersion) String

func (t TLSVersion) String() string

String is the same as AsStr.

func (*TLSVersion) UnmarshalJSON

func (t *TLSVersion) UnmarshalJSON([]byte) error

UnmarshalJSON always fails: see ErrJSONDecodingUnsupported.

Directories

Path Synopsis
Package mailbox iterates over the two common on-disk mailbox formats.
Package mailbox iterates over the two common on-disk mailbox formats.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL