Lots of cleanups and refactoring

This commit is contained in:
Winni Neessen 2022-03-10 16:19:51 +01:00
parent 59a1d14ca7
commit 57ebb171c5
Signed by: wneessen
GPG key ID: 385AC9889632126E
6 changed files with 163 additions and 56 deletions

View file

@ -4,10 +4,12 @@
<option name="autoReloadType" value="ALL" />
</component>
<component name="ChangeListManager">
<list default="true" id="b79e8e7a-d892-4ce4-8bf4-f9e45415b803" name="Changes" comment="Implemented SMTP AUTH">
<list default="true" id="b79e8e7a-d892-4ce4-8bf4-f9e45415b803" name="Changes" comment="Better context and connection handling">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client.go" beforeDir="false" afterPath="$PROJECT_DIR$/client.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/main.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/header.go" beforeDir="false" afterPath="$PROJECT_DIR$/header.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/mailmsg.go" beforeDir="false" afterPath="$PROJECT_DIR$/mailmsg.go" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
@ -94,7 +96,8 @@
<component name="VcsManagerConfiguration">
<MESSAGE value="Progress" />
<MESSAGE value="Implemented SMTP AUTH" />
<option name="LAST_COMMIT_MESSAGE" value="Implemented SMTP AUTH" />
<MESSAGE value="Better context and connection handling" />
<option name="LAST_COMMIT_MESSAGE" value="Better context and connection handling" />
</component>
<component name="VgoProject">
<integration-enabled>true</integration-enabled>

View file

@ -8,7 +8,24 @@ full-fledged mail library.
**This library is "WIP" an should not be considered "production ready", yet.**
go-mail follows idiomatic Go style and best practice.
go-mail follows idiomatic Go style and best practice. It's only dependency is the Go Standard Library.
It combines a lot of functionality from the standard library to give easy and convenient access to
mail and SMTP related tasks.
## Features
Some of the features of this library:
* [X] Only Standard Library dependant
* [X] Modern, idiotmatic Go
* [X] SSL/TLS support
* [X] StartTLS support with different policies
* [X] Makes use of contexts for a better control flow and timeout/cancelation handling
* [X] SMTP Auth support (LOGIN, PLAIN, CRAM-MD5, DIGEST-MD5)
* [X] RFC5322 compliant mail address validation
* [X] Support for common mail header field generation (Message-ID, Date, Bulk-Precedence, etc.)
* [X] Reusing the same SMTP connection to send multiple mails
* [ ] Support for different encodings
* [ ] Support for attachments
* [ ] Go template support
## Example
```go
@ -30,17 +47,11 @@ func main() {
fmt.Printf("failed to create new client: %s\n", err)
os.Exit(1)
}
defer c.Close()
ctx, cfn := context.WithCancel(context.Background())
defer cfn()
if err := c.DialWithContext(ctx); err != nil {
if err := c.DialAndSend(); err != nil {
fmt.Printf("failed to dial: %s\n", err)
os.Exit(1)
}
if err := c.Send(); err != nil {
fmt.Printf("failed to send: %s\n", err)
os.Exit(1)
}
}
```

View file

@ -220,11 +220,6 @@ func (c *Client) SetSMTPAuthCustom(sa smtp.Auth) {
c.sa = sa
}
// Close closes the connection cto the SMTP server
func (c *Client) Close() error {
return c.sc.Close()
}
// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname
func (c *Client) setDefaultHelo() error {
hn, err := os.Hostname()
@ -301,6 +296,18 @@ func (c *Client) Send() error {
return nil
}
// Close closes the Client connection
func (c *Client) Close() error {
if err := c.checkConn(); err != nil {
return err
}
if err := c.sc.Close(); err != nil {
return fmt.Errorf("failed to close SMTP client: %w", err)
}
return nil
}
// DialAndSend establishes a connection to the SMTP server with a
// default context.Background and sends the mail
func (c *Client) DialAndSend() error {

View file

@ -16,20 +16,31 @@ func main() {
tu := os.Getenv("TEST_USER")
tp := os.Getenv("TEST_PASS")
m := mail.NewMsg()
if err := m.From(`Winni Neessen <wn@neessen.net>`); err != nil {
fmt.Printf("failed to set FROM addres: %s", err)
os.Exit(1)
}
m.ToIgnoreInvalid("test@test.de", "foo@bar.de", "blubb@blah.com")
m.CcIgnoreInvalid("cc@test.de", "bar.de", "cc@blah.com")
m.BccIgnoreInvalid("bcc@test.de", "bcc@blah.com")
m.SetMessageID()
m.SetDate()
m.SetBulk()
m.Header()
c, err := mail.NewClient(th, mail.WithTimeout(time.Millisecond*500), mail.WithTLSPolicy(mail.TLSMandatory),
mail.WithSMTPAuth(mail.SMTPAuthDigestMD5), mail.WithUsername(tu), mail.WithPassword(tp))
if err != nil {
fmt.Printf("failed to create new client: %s\n", err)
os.Exit(1)
}
m := mail.NewMsg()
m.From("wn@neessen.net")
m.To("test@test.de", "foo@bar.de", "blubb@blah.com")
m.Cc("cc@test.de", "cc@bar.de", "cc@blah.com")
m.SetMessageID()
m.SetBulk()
m.Header()
defer func() {
if err := c.Close(); err != nil {
fmt.Printf("failed to close: %s\n", err)
os.Exit(1)
}
}()
if err := c.DialAndSend(); err != nil {
fmt.Printf("failed to dial: %s\n", err)

View file

@ -1,22 +1,13 @@
package mail
// Header represents a mail header field name
// Header represents a generic mail header field name
type Header string
// List of common header field names
// AddrHeader represents a address related mail Header field name
type AddrHeader string
// List of common generic header field names
const (
// HeaderFrom is the "From" header field
HeaderFrom Header = "From"
// HeaderTo is the "Receipient" header field
HeaderTo Header = "To"
// HeaderCc is the "Carbon Copy" header field
HeaderCc Header = "Cc"
// HeaderPrecedence is the "Precedence" header field
HeaderPrecedence Header = "Precedence"
// HeaderDate represents the "Date" field
// See: https://www.rfc-editor.org/rfc/rfc822#section-5.1
HeaderDate Header = "Date"
@ -24,4 +15,25 @@ const (
// HeaderMessageID represents the "Message-ID" field for message identification
// See: https://www.rfc-editor.org/rfc/rfc1036#section-2.1.5
HeaderMessageID Header = "Message-ID"
// HeaderPrecedence is the "Precedence" genHeader field
HeaderPrecedence Header = "Precedence"
// HeaderSubject is the "Subject" genHeader field
HeaderSubject Header = "Subject"
)
// List of common generic header field names
const (
// HeaderBcc is the "Blind Carbon Copy" genHeader field
HeaderBcc AddrHeader = "Bcc"
// HeaderCc is the "Carbon Copy" genHeader field
HeaderCc AddrHeader = "Cc"
// HeaderFrom is the "From" genHeader field
HeaderFrom AddrHeader = "From"
// HeaderTo is the "Receipient" genHeader field
HeaderTo AddrHeader = "To"
)

View file

@ -3,6 +3,7 @@ package mail
import (
"fmt"
"math/rand"
"net/mail"
"os"
"time"
)
@ -15,42 +16,97 @@ type Msg struct {
// encoder represents a mime.WordEncoder from the std lib
//encoder mime.WordEncoder
// header is a slice of strings that the different mail header fields
header map[Header][]string
// genHeader is a slice of strings that the different generic mail Header fields
genHeader map[Header][]string
// addrHeader is a slice of strings that the different mail AddrHeader fields
addrHeader map[AddrHeader][]*mail.Address
}
// NewMsg returns a new Msg pointer
func NewMsg() *Msg {
m := &Msg{
charset: "UTF-8",
header: make(map[Header][]string),
charset: "UTF-8",
genHeader: make(map[Header][]string),
addrHeader: make(map[AddrHeader][]*mail.Address),
}
return m
}
// SetHeader sets a header field of the Msg
// SetHeader sets a generic header field of the Msg
func (m *Msg) SetHeader(h Header, v ...string) {
m.genHeader[h] = v
}
// SetAddrHeader sets an address related header field of the Msg
func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error {
var al []*mail.Address
for _, av := range v {
a, err := mail.ParseAddress(av)
if err != nil {
return fmt.Errorf("failed to parse mail address header %q: %w", av, err)
}
al = append(al, a)
}
switch h {
case HeaderFrom:
m.header[h] = []string{v[0]}
m.addrHeader[h] = []*mail.Address{al[0]}
default:
m.header[h] = v
m.addrHeader[h] = al
}
return nil
}
// From sets the From: address of the Msg
func (m *Msg) From(f string) {
m.SetHeader(HeaderFrom, f)
// SetAddrHeaderIgnoreInvalid sets an address related header field of the Msg and ignores invalid address
// in the validation process
func (m *Msg) SetAddrHeaderIgnoreInvalid(h AddrHeader, v ...string) {
var al []*mail.Address
for _, av := range v {
a, err := mail.ParseAddress(av)
if err != nil {
continue
}
al = append(al, a)
}
m.addrHeader[h] = al
}
// To sets the To: addresses of the Msg
func (m *Msg) To(t ...string) {
m.SetHeader(HeaderTo, t...)
// From takes and validates a given mail address and sets it as "From" genHeader of the Msg
func (m *Msg) From(f string) error {
return m.SetAddrHeader(HeaderFrom, f)
}
// Cc sets the Cc: addresses of the Msg
func (m *Msg) Cc(c ...string) {
m.SetHeader(HeaderCc, c...)
// To takes and validates a given mail address list sets the To: addresses of the Msg
func (m *Msg) To(t ...string) error {
return m.SetAddrHeader(HeaderTo, t...)
}
// ToIgnoreInvalid takes and validates a given mail address list sets the To: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func (m *Msg) ToIgnoreInvalid(t ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderTo, t...)
}
// Cc takes and validates a given mail address list sets the Cc: addresses of the Msg
func (m *Msg) Cc(c ...string) error {
return m.SetAddrHeader(HeaderCc, c...)
}
// CcIgnoreInvalid takes and validates a given mail address list sets the Cc: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func (m *Msg) CcIgnoreInvalid(c ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderCc, c...)
}
// Bcc takes and validates a given mail address list sets the Bcc: addresses of the Msg
func (m *Msg) Bcc(b ...string) error {
return m.SetAddrHeader(HeaderBcc, b...)
}
// BccIgnoreInvalid takes and validates a given mail address list sets the Bcc: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func (m *Msg) BccIgnoreInvalid(b ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderBcc, b...)
}
// SetMessageID generates a random message id for the mail
@ -73,16 +129,23 @@ func (m *Msg) SetMessageIDWithValue(v string) {
m.SetHeader(HeaderMessageID, v)
}
// SetBulk sets the "Precedence: bulk" header which is recommended for
// SetBulk sets the "Precedence: bulk" genHeader which is recommended for
// automated mails like OOO replies
// See: https://www.rfc-editor.org/rfc/rfc2076#section-3.9
func (m *Msg) SetBulk() {
m.SetHeader(HeaderPrecedence, "bulk")
}
// SetDate sets the Date genHeader field to the current time in a valid format
func (m *Msg) SetDate() {
ts := time.Now().Format(time.RFC1123Z)
m.SetHeader(HeaderDate, ts)
}
// Header does something
// FIXME: This is only here to quickly show the set headers for debugging purpose. Remove me later
func (m *Msg) Header() {
fmt.Printf("%+v\n", m.header)
fmt.Printf("Address header: %+v\n", m.addrHeader)
fmt.Printf("Generic header: %+v\n", m.genHeader)
}