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

mailparse

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 1, 2026 License: MIT Imports: 13 Imported by: 0

README

MailKite

MailKite mail-parse (Go)

Deterministic, PII-free fingerprints for email parse failures — the same broken message yields the same hash in every language, so dedup and roll-up just work.

Docs · Libraries · TypeScript

CI Go Reference MIT

Read-only mirror. This repo is a generated, release-time mirror of the MailKite monorepo (the private source of truth) — development doesn't happen here. Install from Go modules and open issues against the MailKite docs.

Why

When a MIME message fails to parse, you want to know which class of failure it is — without ever touching the words, addresses, or subjects inside it. ComputeSignature turns a failure into a deterministic, PII-free fingerprint built only from structural features (scope, diagnostic codes, content type, transfer encoding, leading body bytes, header names, mailer family). The same failure produces the same FNV-1a hash in TypeScript, Python, and Go — byte-for-byte — so you can dedup across services, roll up "a whole class is failing" vs. a precise group, and feed both the cold-loop issue body and the hot-loop match from one fingerprint.

Install

go get github.com/mailkite/mail-parse-go

Quickstart

package main

import (
	"fmt"

	mailparse "github.com/mailkite/mail-parse-go"
)

func main() {
	sig := mailparse.ComputeSignature(mailparse.FailureEvent{
		Scope:            "part",
		DiagnosticCodes:  []string{"BOUNDARY_NOT_CLOSED"},
		ContentType:      "application/ms-tnef",
		TransferEncoding: "base64",
	})

	fmt.Println(sig.Hash) // f55154fda8f2cadd — same in TS, Python, and Go

	for _, r := range sig.Rollup {
		fmt.Printf("%-12s %s\n", r.Level, r.Hash) // scope → contentType → byteSignature → mailer
	}
}

ComputeSignature returns a FailureSignature with the finest-granularity Hash (the primary dedup key), the normalized Features, and a coarse-to-fine Rollup. The package also exports the building blocks Fnv1a64 and NormalizeMailer.

All MailKite libraries

Library Language Package
mail-parse TypeScript @mailkite/mail-parse
mail-parse-py Python mailkite-mail-parse
mail-parse-go Go github.com/mailkite/mail-parse-go

License

MIT — see LICENSE.

Documentation

Index

Constants

View Source
const LibVersion = "0.0.0"

LibVersion is the default library version embedded in a signature's features.

Variables

This section is empty.

Functions

func DecodeBase64

func DecodeBase64(raw []byte) []byte

DecodeBase64 decodes a base64 body. Tolerant of whitespace and stray non-alphabet bytes.

func DecodeCharset

func DecodeCharset(data []byte, charset string) (string, bool, string)

DecodeCharset decodes bytes to a string using a charset label. Returns (text, ok, label). ok is false (with a utf-8 best-effort string) when the label is unsupported, so the caller can record a diagnostic.

func DecodeEncodedWords

func DecodeEncodedWords(input string) string

DecodeEncodedWords decodes RFC 2047 encoded-words. Adjacent words concatenate; whitespace strictly between two encoded words is dropped (RFC 2047 §6.2).

func DecodeQuotedPrintable

func DecodeQuotedPrintable(raw []byte) []byte

DecodeQuotedPrintable decodes a quoted-printable body (RFC 2045 §6.7).

func DecodeTransfer

func DecodeTransfer(raw []byte, encoding string) []byte

DecodeTransfer applies a Content-Transfer-Encoding. Unknown encodings pass through (7bit/8bit/binary).

func Fnv1a64

func Fnv1a64(s string) string

Fnv1a64 computes an FNV-1a 64-bit hash of s and returns it as 16 lowercase hex digits. A fast, portable, non-crypto fingerprint — fine for grouping. Must match the TS/Python implementations.

func GetHeader

func GetHeader(headers []Header, name string) (string, bool)

GetHeader is a case-insensitive header lookup (first match). ok reports whether it was present.

func Matches

func Matches(spec MatchSpec, meta *PartMeta, body []byte) bool

Matches reports whether spec matches this part. All present fields must match (AND).

func NormalizeMailer

func NormalizeMailer(mailer string) string

NormalizeMailer coarsely normalizes an X-Mailer / User-Agent into a family token (PII-free, low-cardinality).

func Specificity

func Specificity(spec MatchSpec) float64

Specificity is a score in [0,1] — more (and more selective) conjuncts -> higher.

func SplitMime

func SplitMime(r io.Reader, handlers SplitHandlers)

SplitMime drives the state machine over r, emitting node events to handlers.

func UnflowFormat

func UnflowFormat(text string, delsp bool) string

UnflowFormat reflows a format=flowed text/plain body.

Types

type Address

type Address struct {
	Address string `json:"address"`
	Name    string `json:"name,omitempty"`
}

Address is a parsed email address. Address is "" if unparseable; Name is RFC 2047 decoded.

func ParseAddressList

func ParseAddressList(raw string) []Address

ParseAddressList parses an address-list header (From/To/Cc/…). Common-case subset: `Name <addr>`, bare `addr`, comma-separated lists, quoted display names. Groups + comments are a follow-up.

func ParseSingleAddress

func ParseSingleAddress(raw string) *Address

ParseSingleAddress returns the first address of a list, or nil.

type ArrayTrace

type ArrayTrace struct {
	Spans []*SpanRecord
}

ArrayTrace is a simple collector — one record per invocation, in START order (parent before child).

func (*ArrayTrace) Start

func (t *ArrayTrace) Start(mw string, meta *PartMeta) Span

Start records a new span and returns it.

type Attachment

type Attachment struct {
	Filename    string `json:"filename"`
	MimeType    string `json:"mimeType"`
	Disposition string `json:"disposition"` // "attachment" | "inline" | ""
	ContentID   string `json:"contentId"`
	Content     []byte `json:"-"`
	Size        int    `json:"size"`
}

Attachment is a decoded attachment (or inline) part.

type ByteSignature

type ByteSignature struct {
	Offset    int
	HexPrefix string
}

ByteSignature is a magic-number match on the decoded body's leading bytes.

type Diagnostic

type Diagnostic struct {
	Code     string `json:"code"`
	Severity string `json:"severity"` // "warn" | "error"
	Scope    string `json:"scope"`    // "envelope" | "structure" | "part"
	Detail   string `json:"detail,omitempty"`
	Path     string `json:"path,omitempty"`
}

Diagnostic is a typed, non-fatal degradation. The parser collects these instead of erroring.

type EmittedPart

type EmittedPart struct {
	Meta *PartMeta
	Body []byte
}

EmittedPart is a part a middleware emits in place of (or in addition to) the one it received.

type FailureEvent

type FailureEvent struct {
	Scope            string
	DiagnosticCodes  []string
	ContentType      string
	Disposition      string
	TransferEncoding string
	BodyHead         []byte
	HeaderNames      []string
	Mailer           string
	StructurePath    string
}

FailureEvent is a failure observed by the caller, before normalization. BodyHead enables a byte-signature.

type FailureFeatures

type FailureFeatures struct {
	LibVersion       string   `json:"libVersion"`
	Scope            string   `json:"scope"`
	DiagnosticCodes  []string `json:"diagnosticCodes"`
	ContentType      string   `json:"contentType,omitempty"`
	Disposition      string   `json:"disposition,omitempty"`
	TransferEncoding string   `json:"transferEncoding,omitempty"`
	// ByteSignature is the hex of the offending part's leading bytes — structure, never content.
	ByteSignature string   `json:"byteSignature,omitempty"`
	HeaderNames   []string `json:"headerNames,omitempty"`
	// MailerFamily is the normalized X-Mailer/User-Agent family, e.g. "outlook".
	MailerFamily  string `json:"mailerFamily,omitempty"`
	StructurePath string `json:"structurePath,omitempty"`
}

FailureFeatures holds the PII-free structural features of a failure.

type FailureSignature

type FailureSignature struct {
	// Hash is the finest-granularity hash — the primary dedup key.
	Hash     string            `json:"hash"`
	Features FailureFeatures   `json:"features"`
	Rollup   []SignatureRollup `json:"rollup"`
}

FailureSignature is the computed fingerprint of a failure.

func ComputeSignature

func ComputeSignature(event FailureEvent) FailureSignature

ComputeSignature computes a deterministic signature + roll-up hashes from a failure event.

func ComputeSignatureVersioned

func ComputeSignatureVersioned(event FailureEvent, libVersion string) FailureSignature

ComputeSignatureVersioned is ComputeSignature with an explicit library version.

type Header struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

Header is one unfolded header line, in document order.

func ParseHeaderBlock

func ParseHeaderBlock(block string) []Header

ParseHeaderBlock parses a raw header block (already split from the body) into ordered, unfolded headers.

type HeaderMatch

type HeaderMatch struct {
	Name    string
	Matches *regexp.Regexp
}

HeaderMatch matches a header value by (case-insensitive) name.

type Line

type Line struct {
	Bytes []byte
	Term  string // "\r\n" | "\n" | "" (last line, no terminator)
}

Line is one physical line: content bytes (no terminator) + the terminator that followed it.

type MatchSpec

type MatchSpec struct {
	ContentType   string
	MainType      string
	Charset       string
	Disposition   string // "inline" | "attachment"
	Filename      *regexp.Regexp
	Header        *HeaderMatch
	ByteSignature *ByteSignature
	Predicate     func(meta *PartMeta, body []byte) bool
}

MatchSpec is a declarative predicate over a part's structural metadata (+ optional body byte-signature).

type Message

type Message struct {
	Headers     []Header               `json:"headers"`
	From        *Address               `json:"from,omitempty"`
	To          []Address              `json:"to"`
	Cc          []Address              `json:"cc"`
	Bcc         []Address              `json:"bcc"`
	ReplyTo     []Address              `json:"replyTo"`
	Subject     *string                `json:"subject"`
	MessageID   *string                `json:"messageId"`
	InReplyTo   *string                `json:"inReplyTo"`
	Date        *string                `json:"date"`
	Text        *string                `json:"text"`
	HTML        *string                `json:"html"`
	Attachments []Attachment           `json:"attachments"`
	Diagnostics []Diagnostic           `json:"diagnostics"`
	Annotations map[string]interface{} `json:"annotations"`
}

Message is the buffered result of Parse — the same shape as the TS package's Message.

func Parse

func Parse(raw []byte, opts *ParseOptions) Message

Parse parses a raw MIME message into a typed Message. Never panics on malformed input — collects diagnostics. opts may be nil.

func ParseReader

func ParseReader(r io.Reader, opts *ParseOptions) Message

ParseReader is Parse over an io.Reader source.

type Middleware

type Middleware struct {
	Name   string
	Phase  string
	Match  MatchSpec
	OnPart func(part *Part, next func())
}

Middleware transforms a part; call next() to continue the chain, or return to short-circuit.

type ParseOptions

type ParseOptions struct {
	Middleware []Middleware
	Trace      TraceHook
}

ParseOptions configures Parse.

type Part

type Part struct {
	Meta        *PartMeta
	Body        []byte
	Text        *string
	HTML        *string
	ClassifyAs  string // "attachment" | "text" | "html" | ""
	Emitted     []EmittedPart
	Context     map[string]interface{}
	Diagnostics []Diagnostic
}

Part is the mutable per-part handle middleware operate on.

func NewPart

func NewPart(meta *PartMeta, body []byte) *Part

NewPart builds a Part for meta with a decoded body.

func (*Part) AddDiagnostic

func (p *Part) AddDiagnostic(d Diagnostic)

AddDiagnostic appends a diagnostic to the part.

type PartMeta

type PartMeta struct {
	Depth       int
	Headers     []Header
	ContentType string
	MainType    string
	Charset     string
	Format      string
	Delsp       bool
	Encoding    string
	Disposition string // "attachment" | "inline" | ""
	Filename    string
	ContentID   string
	Boundary    string
	IsMultipart bool
	Path        string
}

PartMeta is the structural metadata the core computes for every MIME node BEFORE middleware runs (Layer 0). Handlers receive *PartMeta, so identity (pointer) keys per-part collectors exactly like the TS Map<PartMeta, ...> / Python identity dict.

type Registry

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

Registry holds and dispatches middleware.

func ToRegistry

func ToRegistry(mws []Middleware) *Registry

ToRegistry normalizes middleware into a Registry, or nil when there's nothing to run.

func (*Registry) RunPart

func (r *Registry) RunPart(part *Part, trace TraceHook)

RunPart runs the matched (part, next) chain. Tolerant: a panicking middleware is contained + recovered.

func (*Registry) Size

func (r *Registry) Size() int

Size returns the number of registered middleware.

func (*Registry) Use

func (r *Registry) Use(mw Middleware) *Registry

Use registers a middleware and returns the registry for chaining.

type SignatureRollup

type SignatureRollup struct {
	// Level is the granularity label, coarse → fine.
	Level string `json:"level"`
	Hash  string `json:"hash"`
}

SignatureRollup is one coarse-to-fine granularity level and its hash.

type Span

type Span interface {
	End(info SpanEnd)
}

Span is returned by TraceHook.Start; End fills in the in/out + status.

type SpanEnd

type SpanEnd struct {
	InSize  int
	OutSize int
	Status  string // "ok" | "error"
	Error   string
}

SpanEnd is the in/out state recorded when a middleware invocation ends.

type SpanRecord

type SpanRecord struct {
	Mw      string
	Path    string
	InSize  int
	OutSize int
	Status  string
	Error   string
}

SpanRecord is one recorded middleware invocation.

type SplitHandlers

type SplitHandlers struct {
	OnNodeStart  func(meta *PartMeta)               // headers complete; PartMeta computed. Fires for every node.
	OnBody       func(meta *PartMeta, chunk []byte) // a chunk of RAW body bytes for a leaf. May fire many.
	OnNodeEnd    func(meta *PartMeta)               // node finished (leaf body delivered, or container closed).
	OnDiagnostic func(d Diagnostic)                 // a non-fatal structural degradation.
}

SplitHandlers holds the callbacks the splitter fires. Any may be nil.

type StructuredField

type StructuredField struct {
	Value  string
	Params map[string]string
}

StructuredField is a parsed structured header: Value (lowercased primary) + Params (lowercased keys).

func ParseStructuredField

func ParseStructuredField(raw string) StructuredField

ParseStructuredField parses a field like `multipart/mixed; boundary="x"; name*=UTF-8”%E2%82%AC`.

type TraceHook

type TraceHook interface {
	Start(mwName string, meta *PartMeta) Span
}

TraceHook is called around every middleware invocation. Start may return nil.

Jump to

Keyboard shortcuts

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