package email
import "git.sr.ht/~shulhan/pakakeh.go/lib/email"
Package email provide a library for working with Internet Message Format as defined in RFC 5322.
The building block of a Message.
+---------------+ | Message | +--------+------+ | Header | Body | +--------+------+
A Body contains one or more MIME,
+------------------+ | MIME | +--------+---------+ | Header | Content | +--------+---------+
A Header contains one or more Field,
+---------------------+ | Field | +------+-------+------+ | Name | Value | Type | +------+-------+------+
Field is parsed line that contains Name and Value separated by colon ':'.
A ContentType is special Field where Name is "Content-Type", and its Value is parsed from string "top/sub; <param>; ...". A ContentType can contains zero or more Param, each separated by ";".
+-------------------+ | ContentType | +-----+-----+-------+ | Top | Sub | Param | +-----+-----+-------+
A Param is parsed string of key and value separated by "=", where value can be quoted, for example `key=value` or `key="quoted value"`.
+----------------------+ | Param | +-----+-------+--------+ | Key | Value | Quoted | +-----+-------+--------+
Notes
In the comment and/or methods of some type, you will see the word "simple" or "relaxed". This method only used for message signing and verification using DKIM (see Message.DKIMSign and Message.DKIMVerify implementation). In short, "simple" return the formatted header or body as is, while "relaxed" return the formatted header or body by trimming the space. See RFC 6376 Section 3.4 or our summary.
Index ¶
- Constants
- Variables
- func IsValidLocal(local []byte) bool
- type Body
- func ParseBody(raw, boundary []byte) (body *Body, rest []byte, err error)
- func (body *Body) Add(mime *MIME)
- func (body *Body) Relaxed() (out []byte)
- func (body *Body) Set(mime *MIME)
- func (body *Body) Simple() (out []byte)
- func (body *Body) String() string
- type ContentType
- func ParseContentType(raw []byte) (ct *ContentType, err error)
- func (ct *ContentType) GetParamValue(name string) string
- func (ct *ContentType) SetBoundary(boundary string)
- func (ct *ContentType) String() string
- type Field
- func ParseField(raw []byte) (field *Field, rest []byte, err error)
- func (field *Field) Relaxed() (out []byte)
- func (field *Field) Simple() (out []byte)
- type FieldType
- type Header
- func ParseHeader(raw []byte) (hdr *Header, rest []byte, err error)
- func (hdr *Header) Boundary() string
- func (hdr *Header) ContentType() *ContentType
- func (hdr *Header) DKIM(n int) (dkimHeader *Header)
- func (hdr *Header) Filter(ft FieldType) (fields []*Field)
- func (hdr *Header) ID() string
- func (hdr *Header) PushTop(f *Field)
- func (hdr *Header) Relaxed() []byte
- func (hdr *Header) Set(ft FieldType, value []byte) (err error)
- func (hdr *Header) SetMultipart() (err error)
- func (hdr *Header) Simple() []byte
- func (hdr *Header) WriteTo(w io.Writer) (n int64, err error)
- type MIME
- func ParseBodyPart(raw, boundary []byte) (mime *MIME, rest []byte, err error)
- func (mime *MIME) String() string
- func (mime *MIME) WriteTo(w io.Writer) (n int64, err error)
- type Mailbox
- func ParseMailbox(raw []byte) (mbox *Mailbox)
- func ParseMailboxes(raw []byte) (mboxes []*Mailbox, err error)
- func (mbox *Mailbox) MarshalJSON() (b []byte, err error)
- func (mbox *Mailbox) String() string
- func (mbox *Mailbox) UnmarshalJSON(b []byte) (err error)
- type Message
- func NewMultipart(from, to, subject, bodyText, bodyHTML []byte) ( msg *Message, err error, )
- func ParseFile(inFile string) (msg *Message, rest []byte, err error)
- func ParseMessage(raw []byte) (msg *Message, rest []byte, err error)
- func (msg *Message) AddCC(mailboxes string) (err error)
- func (msg *Message) AddTo(mailboxes string) (err error)
- func (msg *Message) CanonBody() (body []byte)
- func (msg *Message) CanonHeader(subHeader *Header, dkimField *Field) []byte
- func (msg *Message) DKIMSign(pk *rsa.PrivateKey, sig *dkim.Signature) (err error)
- func (msg *Message) DKIMVerify() (*dkim.Status, error)
- func (msg *Message) Pack() (out []byte, err error)
- func (msg *Message) SetBodyHTML(content []byte) (err error)
- func (msg *Message) SetBodyText(content []byte) (err error)
- func (msg *Message) SetCC(mailboxes string) (err error)
- func (msg *Message) SetFrom(mailbox string) (err error)
- func (msg *Message) SetID(id string)
- func (msg *Message) SetSubject(subject string)
- func (msg *Message) SetTo(mailboxes string) (err error)
- func (msg *Message) String() string
- type Param
Examples ¶
Constants ¶
const ( // Parameter for Text Media Type, RFC 2046 section 4.1. ParamNameCharset = `charset` // Parameters for "application/octet-stream", RFC 2046 section 4.5.1. ParamNameType = `type` ParamNamePadding = `padding` // Parameter for "multipart", RFC 2046 section 5.1. ParamNameBoundary = `boundary` // Parameters for "multipart/partial", RFC 2046 section 5.2.2. ParamNameID = `id` ParamNameNumber = `number` ParamNameTotal = `total` )
List of known parameter name in header field's value.
const DateFormat = "Mon, 2 Jan 2006 15:04:05 -0700"
DateFormat define the default date layout in email.
Variables ¶
Epoch return the UNIX timestamp in seconds.
This variable is exported to allow function that use time can be tested with fixed, predictable value.
Functions ¶
func IsValidLocal ¶
IsValidLocal will return true if local part contains valid characters. Local part must,
- start or end without dot character,
- contains only printable US-ASCII characters, excluding special characters
- no multiple sequence of dots.
List of special characters,
"(" / ")" / "<" / ">" / "[" / "]" / ":" / ";" / "@" / "\" / "," / "." / DQUOTE
Types ¶
type Body ¶
type Body struct { // Parts contains one or more message body. Parts []*MIME // contains filtered or unexported fields }
Body represent single or multiple message body parts.
func ParseBody ¶
ParseBody parse the the raw message's body using boundary.
func (*Body) Add ¶
Add new MIME part to the body.
func (*Body) Relaxed ¶
Relaxed canonicalize the original body with "relaxed" algorithm as defined in RFC 6376 section 3.4.4. It remove all trailing whitespaces, reduce sequence of whitespaces inside line into single space, and remove all empty line at the end of body. If body is not empty and not end with CRLF, a CRLF is added.
This function is expensive for message with large body, its better if we call it once and store it somewhere.
func (*Body) Set ¶
Set replace the MIME content-type with new one, if its exist; otherwise append it.
func (*Body) Simple ¶
Simple canonicalize the original body with "simple" algorithm as defined in RFC 6376 section 3.4.3. Basically, it converts "*CRLF" at the end of body to a single CRLF. If no message body or no trailing CRLF, a CRLF is added.
func (*Body) String ¶
String return text representation of Body.
type ContentType ¶
ContentType represent MIME header "Content-Type" field.
func ParseContentType ¶
func ParseContentType(raw []byte) (ct *ContentType, err error)
ParseContentType parse content type from raw bytes.
Code:play
Output:Example¶
package main
import (
"fmt"
"log"
"git.sr.ht/~shulhan/pakakeh.go/lib/email"
)
func main() {
var (
raw = []byte(`text/plain; key1=val1; key2="value 2"`)
ct *email.ContentType
err error
)
ct, err = email.ParseContentType(raw)
if err != nil {
log.Fatal(err)
}
fmt.Println(ct.String())
}
text/plain; key1=val1; key2="value 2"
func (*ContentType) GetParamValue ¶
func (ct *ContentType) GetParamValue(name string) string
GetParamValue return parameter value related to specific name.
Code:play
Output:Example¶
package main
import (
"fmt"
"log"
"git.sr.ht/~shulhan/pakakeh.go/lib/email"
)
func main() {
var (
raw = []byte(`text/plain; key1=val1; key2="value 2"`)
ct *email.ContentType
err error
)
ct, err = email.ParseContentType(raw)
if err != nil {
log.Fatal(err)
}
var key = `notexist`
fmt.Printf("%s=%q\n", key, ct.GetParamValue(key))
key = `KEY1`
fmt.Printf("%s=%q\n", key, ct.GetParamValue(key))
key = `key2`
fmt.Printf("%s=%q\n", key, ct.GetParamValue(key))
}
notexist=""
KEY1="val1"
key2="value 2"
func (*ContentType) SetBoundary ¶
func (ct *ContentType) SetBoundary(boundary string)
SetBoundary set or replace the Value for Key "boundary".
func (*ContentType) String ¶
func (ct *ContentType) String() string
String return text representation of content type with its parameters.
type Field ¶
type Field struct { // Name contains "relaxed" canonicalization of field name. Name string // Value contains "relaxed" canonicalization of field value. Value string // Params contains unpacked parameter from Value. // Not all content type has parameters. Params []Param // Type of field, the numeric representation of field name. Type FieldType // contains filtered or unexported fields }
Field represent field name and value in header.
func ParseField ¶
ParseField create and initialize Field by parsing a single line message header.
If raw input contains multiple lines, the rest of lines will be returned.
On error, it will return nil Field, and rest will contains the beginning of invalid input.
func (*Field) Relaxed ¶
Relaxed return the relaxed canonicalization of field name and value.
func (*Field) Simple ¶
Simple return the simple canonicalization of field name and value.
type FieldType ¶
type FieldType int
FieldType represent numerical identification of field name.
const ( FieldTypeOptional FieldType = iota // The origination date field, RFC 5322 section 3.6.1. FieldTypeDate // Originator fields, RFC 5322 section 3.6.2. FieldTypeFrom FieldTypeSender FieldTypeReplyTo // Destination fields, RFC 5322 section 3.6.3. FieldTypeTo FieldTypeCC FieldTypeBCC // Identitication fields, RFC 5322 section 3.6.4. FieldTypeMessageID FieldTypeInReplyTo FieldTypeReferences // Informational fields, RFC 5322 section 3.6.5. FieldTypeSubject FieldTypeComments FieldTypeKeywords // Resent fields, RFC 5322 section 3.6.6. FieldTypeResentDate FieldTypeResentFrom FieldTypeResentSender FieldTypeResentTo FieldTypeResentCC FieldTypeResentBCC FieldTypeResentMessageID // Trace fields, RFC 5322 section 3.6.7. FieldTypeReturnPath FieldTypeReceived // MIME header fields, RFC 2045 FieldTypeMIMEVersion FieldTypeContentType FieldTypeContentTransferEncoding FieldTypeContentID FieldTypeContentDescription // DKIM Signature, RFC 6376. FieldTypeDKIMSignature )
List of valid email envelope headers.
type Header ¶
type Header struct { // Fields is ordered from top to bottom, the first field in message // header is equal to the first element in slice. // // We are not using map here it to prevent the header being reordered // when packing the message back into raw format. Fields []*Field }
Header represent header of message. It contains list of field in message.
func ParseHeader ¶
ParseHeader parse the raw header from top to bottom.
Raw header that start with CRLF indicate an empty header. In this case, it will return nil Header, indicating that no header was parsed, and the leading CRLF is removed on returned "rest".
The raw header may end with optional CRLF, an empty line that separate header from body of message.
On success it will return the rest of raw input (possible message's body) without leading CRLF.
func (*Header) Boundary ¶
Boundary return the message body boundary defined in Content-Type. If no field Content-Type or no boundary it will return nil.
func (*Header) ContentType ¶
func (hdr *Header) ContentType() *ContentType
ContentType return the unpacked value of field "Content-Type", or nil if no field Content-Type exist or there is an error when unpacking.
func (*Header) DKIM ¶
DKIM return sub-header of the "n" DKIM-Signature, start from the top. If no DKIM-Signature found it will return nil.
For example, to get the second DKIM-Signature from the top, call it with "n=2", but if no second DKIM-Signature it will return nil.
func (*Header) Filter ¶
Filter specific field type. If multiple fields type exist it will
return all of them.
Code:play
Output:Example¶
package main
import (
"fmt"
"log"
"git.sr.ht/~shulhan/pakakeh.go/lib/email"
)
func main() {
// Overwrite the email.Epoch to make the example works.
email.Epoch = func() int64 {
return 1645600000
}
var (
fromAddress = []byte("Noreply <noreply@example.com>")
toAddresses = []byte("John <john@example.com>, Jane <jane@example.com>")
subject = []byte(`Example subject`)
bodyText = []byte(`Email body as plain text`)
bodyHTML = []byte(`Email body as <b>HTML</b>`)
msg *email.Message
err error
)
msg, err = email.NewMultipart(
fromAddress,
toAddresses,
subject,
bodyText,
bodyHTML,
)
if err != nil {
log.Fatal(err)
}
_, _ = msg.Pack()
var (
fields []*email.Field
field *email.Field
)
fields = msg.Header.Filter(email.FieldTypeFrom)
for _, field = range fields {
fmt.Printf("%s: %s\n", field.Name, field.Value)
}
}
from: Noreply <noreply@example.com>
func (*Header) ID ¶
ID return the Message-ID or empty if not exist.
func (*Header) PushTop ¶
PushTop put the field at the top of header.
func (*Header) Relaxed ¶
Relaxed canonicalize the header using "relaxed" algorithm and return it.
func (*Header) Set ¶
Set the header's value based on specific type. If no field type found, the new field will be added to the list.
func (*Header) SetMultipart ¶
SetMultipart make the header a multipart bodies with boundary.
func (*Header) Simple ¶
Simple canonicalize the header using "simple" algorithm.
func (*Header) WriteTo ¶
WriteTo the header into w. The header does not end with an empty line to allow multiple Header written multiple times.
type MIME ¶
MIME represent part of message body with their header and content.
func ParseBodyPart ¶
ParseBodyPart parse one body part using boundary and return the rest of body.
func (*MIME) String ¶
String return string representation of MIME object.
func (*MIME) WriteTo ¶
WriteTo write the MIME header and content into Writer w.
type Mailbox ¶
type Mailbox struct { Address string // address contains the combination of "local@domain" Name string Local string Domain string // contains filtered or unexported fields }
Mailbox represent an invidual mailbox.
func ParseMailbox ¶
ParseMailbox parse the raw address(es) and return the first mailbox in the
list.
If the raw parameter is empty or no mailbox present or mailbox format is
invalid, it will return nil.
Code:
Output:Example¶
{
fmt.Printf("%v\n", ParseMailbox([]byte("local")))
fmt.Printf("%v\n", ParseMailbox([]byte("Name <domain>")))
fmt.Printf("%v\n", ParseMailbox([]byte("local@domain")))
fmt.Printf("%v\n", ParseMailbox([]byte("Name <local@domain>")))
// Output:
// <nil>
// Name <@domain>
// <local@domain>
// Name <local@domain>
}
<nil>
Name <@domain>
<local@domain>
Name <local@domain>
func ParseMailboxes ¶
ParseMailboxes parse raw address into single or multiple mailboxes. Raw address can be a group of address, list of mailbox, or single mailbox.
A group of address have the following syntax,
DisplayName ":" mailbox-list ";" [comment]
List of mailbox (mailbox-list) have following syntax,
mailbox *("," mailbox)
A single mailbox have following syntax,
[DisplayName] ["<"] local "@" domain [">"]
The angle bracket is optional, but both must be provided.
DisplayName, local, and domain can have comment before and/or after it,
[comment] text [comment]
A comment have the following syntax,
"(" text [comment] ")"
func (*Mailbox) MarshalJSON ¶
MarshalJSON encode the Mailbox into JSON string.
func (*Mailbox) String ¶
String return the text representation of mailbox.
func (*Mailbox) UnmarshalJSON ¶
UnmarshalJSON decode JSON string into Mailbox.
type Message ¶
type Message struct { DKIMSignature *dkim.Signature Header Header Body Body // contains filtered or unexported fields }
Message represent an unpacked internet message format.
func NewMultipart ¶
NewMultipart create multipart email message with text and HTML bodies.
func ParseFile ¶
ParseFile parse message from file.
func ParseMessage ¶
ParseMessage parse the raw message header and body.
func (*Message) AddCC ¶
AddCC add one or more recipients to the message header CC.
func (*Message) AddTo ¶
AddTo add one or more recipients to the mesage header To.
func (*Message) CanonBody ¶
CanonBody return the canonical representation of Message.
func (*Message) CanonHeader ¶
CanonHeader generate the canonicalization of sub-header and DKIM-Signature field.
func (*Message) DKIMSign ¶
DKIMSign sign the message using the private key and signature. The only required fields in signature is SDID and Selector, any other required fields that are empty will be initialized with default values.
Upon calling this function, any field values in header and body MUST be already encoded.
func (*Message) DKIMVerify ¶
DKIMVerify verify the message signature using DKIM.
func (*Message) Pack ¶
Pack the message for sending.
This method will set the Date header if its not exist, using the local time; and the message-id header if its not exist using the following format:
<epoch>.<random-8-chars>@<local-hostname>
The message content type is automatically set based on the Body parts. If the Body only contain text part, the generated content-type will be set to text/plain. If the Body only contain HTML part, the generated content-type will be set to text/html. If both the text and HTML parts exist, the generated content-type will be set to multipart/alternative.
func (*Message) SetBodyHTML ¶
SetBodyHTML set or replace the message's body HTML content.
func (*Message) SetBodyText ¶
SetBodyText set or replace the message body text content.
func (*Message) SetCC ¶
SetCC set or replace the message header CC with one or more mailboxes. See AddCC to add another recipient to the CC header.
func (*Message) SetFrom ¶
SetFrom set or replace the message header From with mailbox. If the mailbox parameter is empty, nothing will changes.
func (*Message) SetID ¶
SetID set or replace the message-id header to id. If the id is empty, nothing will changes.
func (*Message) SetSubject ¶
SetSubject set or replace the subject. It will do nothing if the subject is empty.
func (*Message) SetTo ¶
SetTo set or replace the message header To with one or more mailboxes. See AddTo to add another recipient to the To header.
func (*Message) String ¶
String return the text representation of Message object.
type Param ¶
type Param struct { Key string Value string Quoted bool // Quoted is true if value is contains special characters. }
Param represent a mapping of key with its value.
Source Files ¶
body.go contenttype.go doc.go email.go field.go fieldtype.go header.go is.go mailbox.go message.go mime.go param.go
Directories ¶
Path | Synopsis |
---|---|
lib/email/dkim | Package dkim provide a library to parse and create DKIM-Signature header field value, as defined in RFC 6376, DomainKeys Identified Mail (DKIM) Signatures. |
lib/email/maildir | Package maildir provide a library to manage message (email), and its folder, using maildir format. |
- Version
- v0.60.0 (latest)
- Published
- Feb 1, 2025
- Platform
- linux/amd64
- Imports
- 18 packages
- Last checked
- 10 minutes ago –
Tools for package owners.