https://github.com/rorycl/letters
Email parsing for go. This fork of github.com/mnako/letters focuses on modularity and speed
https://github.com/rorycl/letters
email golang mime parse parser rfc2045 rfc5322
Last synced: 5 months ago
JSON representation
Email parsing for go. This fork of github.com/mnako/letters focuses on modularity and speed
- Host: GitHub
- URL: https://github.com/rorycl/letters
- Owner: rorycl
- License: mit
- Created: 2025-02-08T20:51:36.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2026-01-15T19:13:25.000Z (5 months ago)
- Last Synced: 2026-01-15T21:45:33.873Z (5 months ago)
- Topics: email, golang, mime, parse, parser, rfc2045, rfc5322
- Language: Go
- Homepage:
- Size: 633 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Letters
Email parsing for go.
v0.1.3 : 01 May 2025 : allow Parser to be shared.
This is a fork of [mnako/letters](https://github.com/mnako/letters), a
minimalistic Golang library for parsing plain and MIME emails. Thanks to
@mnako and contributors, letters has great support for languages other
than English, text and transfer encodings.
This fork (at [#9d1a5, v0.2.3](https://github.com/mnako/letters/commit/db1b793c8119d8ed3d575f44df4391262829d1a5))
of letters focuses on extensibility through modularisation as set out in
[PR #124](https://github.com/mnako/letters/pull/124). This library also
provides improved performance and customisation through user-defined
funcs, for example for efficient inline and attachment file processing.
Future plans include making parsing errors, such as for invalid email
addresses, optionally non-fatal.
See [mailboxoperator](https://github.com/rorycl/mailboxoperator) for a
convenient way of iterating over emails in mailbox or maildir format. An
example client for mailboxoperator is
[mailfinder](https://github.com/rorycl/mailfinder), which also uses this
module.
## Quickstart
Install
```
go get github.com/rorycl/letters@latest
```
Parse an email:
```go
reader := someEmailIOReaderProvider() // see mailboxoperator
p := letters.NewParser()
parsedEmail, err := p.Parse(reader)
// &email.Email{
// Headers: email.Headers{
// Date: time.Time(time.Date(2019, 4, 1, 0, 55, 0, 0, time.UTC)),
// Sender: &mail.Address{
// Name: "อลิซ ผู้ส่งจดหมาย",
// Address: "alis.phusngcdhmay@example.com",
// },
// From: []*mail.Address{
// &mail.Address{
// Name: "อลิซ ผู้ส่งจดหมาย",
// Address: "alis.phusngcdhmay@example.com",
// },
// },
// To: []*mail.Address{
// &mail.Address{
// Name: "บ๊อบ ผู้รับ",
// Address: "bob.phurab@example.com",
// },
// },
// Cc: []*mail.Address{
// &mail.Address{
// Name: "แดน ผู้รับ",
// Address: "dan.phurab@example.com",
// },
// },
// MessageID: "Message-Id-1@example.com",
// InReplyTo: []string{
// "Message-Id-0@example.com",
// },
// References: []string{
// "Message-Id-0@example.com",
// },
// Subject: "📧 Test แพนแกรมภาษาไทย",
// Comments: "Message Header Comment",
// Keywords: []string{
// "Keyword 1",
// "Keyword 2",
// },
// ResentDate: time.Time(time.Date(2019, 4, 1, 0, 55, 0, 0, time.UTC)),
// ExtraHeaders: map[string][]string{
// "X-Clacks-Overhead": []string{
// "GNU Terry Pratchett",
// },
// },
// ContentInfo: &email.ContentInfo{
// Type: "multipart/mixed",
// TypeParams: map[string]string{
// "boundary": "MixedBoundaryString",
// "charset": "tis-620",
// },
// Disposition: "",
// DispositionParams: map[string]string(nil), // p0
// TransferEncoding: "base64",
// ID: "",
// Charset: "tis-620",
// },
// Received: nil,
// },
// Text: ""
// EnrichedText: "เป็นมนุษย์สุดประเสริฐเลิศคุณค่า ..."
// HTML: ""
// Files: []*email.File{
// &email.File{
// FileType: "inline",
// Name: "inline-jpg-image-without-disposition.jpg",
// ContentInfo: &email.ContentInfo{
// Type: "image/jpeg",
// TypeParams: map[string]string{
// "name": "inline-jpg-image-without-disposition.jpg",
// },
// Disposition: "",
// DispositionParams: map[string]string(nil), // p0
// TransferEncoding: "base64",
// ID: "",
// Charset: "tis-620",
// },
// Data: []byte{
// 239, 191, 189, 224, 184, 184, 239, 191, 189, ...
// },
// },
// },
// &email.File{
// FileType: "attachment",
// Name: "attached-pdf-filename.pdf",
// ContentInfo: &email.ContentInfo{
// Type: "application/pdf",
// TypeParams: map[string]string{
// "name": "attached-pdf-name.pdf",
// },
// Disposition: "attachment",
// DispositionParams: map[string]string{
// "filename": "attached-pdf-filename.pdf",
// },
// TransferEncoding: "base64",
// ID: "",
// Charset: "tis-620",
// },
// Data: []byte{
// 37, 80, 68, 70, 45, 49, 46, 13, 116, 114, 97, ...
// },
// },
// }
```
## Options
Various options are provided for customising the Parser, including:
```go
// skip content types
func WithSkipContentTypes(skipContentTypes []string) Opt
// provide a custom address processing function
func WithCustomAddressFunc(af func(string) (*mail.Address, error)) Opt
// provide a custom processing function for string lists of addresses
func WithCustomAddressesFunc(af func(list string) ([]*mail.Address, error)) Opt
// provide a custom date processing function
func WithCustomDateFunc(df func(string) (time.Time, error)) Opt
// provide a custom file processing function
func WithCustomFileFunc(ff func(*email.File) error) Opt
// save files to the stated directory (an example of WithCustomFileFunc)
func WithSaveFilesToDirectory(dir string) Opt
// only process headers
func WithHeadersOnly() Opt
// skip processing attachments
func WithoutAttachments() Opt
// show verbose processing info (currently a noop)
func WithVerbose() Opt
```
More than one option can be supplied.
The `WithoutAttachments` and `WithHeadersOnly` options determine if only part
of an email will be processed.
The `WithSkipContentTypes` allows the user to skip processing MIME
message parts with the supplied content-types.
The date and address "With" options allow the provision of custom funcs to
override the `net/mail` funcs normally used. For example it might be necessary
to extend the date parsing capabilities to deal with poorly formatted date
strings produced by older SMTP servers.
The `WithCustomFileFunc` allows the provision of a custom func for saving,
filtering and/or processing of inline or attached files without reading them
first into an `email.File.Data` []byte slice first, which is the default
behaviour. The `WithSaveFilesToDirectory` option is an example of such a custom
func.
As shown in the [parser/optspkg_test.go](parser/optspkg_test.go) package
test, `WithCustomFileFunc` can be used to, for example, only process
`image/jpeg` files. More examples are shown in
[parser/opts_test.go](parser/opts_test.go), for example:
```go
opt := parser.WithHeadersOnly() // the headers only option
p := letters.NewParser(opt, parser.WithVerbose()) // options can be chained
parsedEmail, err := p.Parse(emailReader)
if err != nil {
return fmt.Errorf("error while parsing email headers: %s", err)
}
```