mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 13:50:49 +01:00
Lots of cleanups and refactoring
This commit is contained in:
parent
59a1d14ca7
commit
57ebb171c5
6 changed files with 163 additions and 56 deletions
|
@ -4,10 +4,12 @@
|
||||||
<option name="autoReloadType" value="ALL" />
|
<option name="autoReloadType" value="ALL" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<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$/.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$/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$/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" />
|
<change beforePath="$PROJECT_DIR$/mailmsg.go" beforeDir="false" afterPath="$PROJECT_DIR$/mailmsg.go" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
@ -94,7 +96,8 @@
|
||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<MESSAGE value="Progress" />
|
<MESSAGE value="Progress" />
|
||||||
<MESSAGE value="Implemented SMTP AUTH" />
|
<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>
|
||||||
<component name="VgoProject">
|
<component name="VgoProject">
|
||||||
<integration-enabled>true</integration-enabled>
|
<integration-enabled>true</integration-enabled>
|
||||||
|
|
29
README.md
29
README.md
|
@ -8,7 +8,24 @@ full-fledged mail library.
|
||||||
|
|
||||||
**This library is "WIP" an should not be considered "production ready", yet.**
|
**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
|
## Example
|
||||||
```go
|
```go
|
||||||
|
@ -30,17 +47,11 @@ func main() {
|
||||||
fmt.Printf("failed to create new client: %s\n", err)
|
fmt.Printf("failed to create new client: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
ctx, cfn := context.WithCancel(context.Background())
|
if err := c.DialAndSend(); err != nil {
|
||||||
defer cfn()
|
|
||||||
|
|
||||||
if err := c.DialWithContext(ctx); err != nil {
|
|
||||||
fmt.Printf("failed to dial: %s\n", err)
|
fmt.Printf("failed to dial: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if err := c.Send(); err != nil {
|
|
||||||
fmt.Printf("failed to send: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
17
client.go
17
client.go
|
@ -220,11 +220,6 @@ func (c *Client) SetSMTPAuthCustom(sa smtp.Auth) {
|
||||||
c.sa = sa
|
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
|
// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname
|
||||||
func (c *Client) setDefaultHelo() error {
|
func (c *Client) setDefaultHelo() error {
|
||||||
hn, err := os.Hostname()
|
hn, err := os.Hostname()
|
||||||
|
@ -301,6 +296,18 @@ func (c *Client) Send() error {
|
||||||
return nil
|
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
|
// DialAndSend establishes a connection to the SMTP server with a
|
||||||
// default context.Background and sends the mail
|
// default context.Background and sends the mail
|
||||||
func (c *Client) DialAndSend() error {
|
func (c *Client) DialAndSend() error {
|
||||||
|
|
27
cmd/main.go
27
cmd/main.go
|
@ -16,20 +16,31 @@ func main() {
|
||||||
tu := os.Getenv("TEST_USER")
|
tu := os.Getenv("TEST_USER")
|
||||||
tp := os.Getenv("TEST_PASS")
|
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),
|
c, err := mail.NewClient(th, mail.WithTimeout(time.Millisecond*500), mail.WithTLSPolicy(mail.TLSMandatory),
|
||||||
mail.WithSMTPAuth(mail.SMTPAuthDigestMD5), mail.WithUsername(tu), mail.WithPassword(tp))
|
mail.WithSMTPAuth(mail.SMTPAuthDigestMD5), mail.WithUsername(tu), mail.WithPassword(tp))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to create new client: %s\n", err)
|
fmt.Printf("failed to create new client: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
m := mail.NewMsg()
|
if err := c.Close(); err != nil {
|
||||||
m.From("wn@neessen.net")
|
fmt.Printf("failed to close: %s\n", err)
|
||||||
m.To("test@test.de", "foo@bar.de", "blubb@blah.com")
|
os.Exit(1)
|
||||||
m.Cc("cc@test.de", "cc@bar.de", "cc@blah.com")
|
}
|
||||||
m.SetMessageID()
|
}()
|
||||||
m.SetBulk()
|
|
||||||
m.Header()
|
|
||||||
|
|
||||||
if err := c.DialAndSend(); err != nil {
|
if err := c.DialAndSend(); err != nil {
|
||||||
fmt.Printf("failed to dial: %s\n", err)
|
fmt.Printf("failed to dial: %s\n", err)
|
||||||
|
|
40
header.go
40
header.go
|
@ -1,22 +1,13 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
// Header represents a mail header field name
|
// Header represents a generic mail header field name
|
||||||
type Header string
|
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 (
|
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
|
// HeaderDate represents the "Date" field
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc822#section-5.1
|
// See: https://www.rfc-editor.org/rfc/rfc822#section-5.1
|
||||||
HeaderDate Header = "Date"
|
HeaderDate Header = "Date"
|
||||||
|
@ -24,4 +15,25 @@ const (
|
||||||
// HeaderMessageID represents the "Message-ID" field for message identification
|
// HeaderMessageID represents the "Message-ID" field for message identification
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc1036#section-2.1.5
|
// See: https://www.rfc-editor.org/rfc/rfc1036#section-2.1.5
|
||||||
HeaderMessageID Header = "Message-ID"
|
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"
|
||||||
)
|
)
|
||||||
|
|
99
mailmsg.go
99
mailmsg.go
|
@ -3,6 +3,7 @@ package mail
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/mail"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -15,42 +16,97 @@ type Msg struct {
|
||||||
// encoder represents a mime.WordEncoder from the std lib
|
// encoder represents a mime.WordEncoder from the std lib
|
||||||
//encoder mime.WordEncoder
|
//encoder mime.WordEncoder
|
||||||
|
|
||||||
// header is a slice of strings that the different mail header fields
|
// genHeader is a slice of strings that the different generic mail Header fields
|
||||||
header map[Header][]string
|
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
|
// NewMsg returns a new Msg pointer
|
||||||
func NewMsg() *Msg {
|
func NewMsg() *Msg {
|
||||||
m := &Msg{
|
m := &Msg{
|
||||||
charset: "UTF-8",
|
charset: "UTF-8",
|
||||||
header: make(map[Header][]string),
|
genHeader: make(map[Header][]string),
|
||||||
|
addrHeader: make(map[AddrHeader][]*mail.Address),
|
||||||
}
|
}
|
||||||
return m
|
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) {
|
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 {
|
switch h {
|
||||||
case HeaderFrom:
|
case HeaderFrom:
|
||||||
m.header[h] = []string{v[0]}
|
m.addrHeader[h] = []*mail.Address{al[0]}
|
||||||
default:
|
default:
|
||||||
m.header[h] = v
|
m.addrHeader[h] = al
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// From sets the From: address of the Msg
|
// SetAddrHeaderIgnoreInvalid sets an address related header field of the Msg and ignores invalid address
|
||||||
func (m *Msg) From(f string) {
|
// in the validation process
|
||||||
m.SetHeader(HeaderFrom, f)
|
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
|
// From takes and validates a given mail address and sets it as "From" genHeader of the Msg
|
||||||
func (m *Msg) To(t ...string) {
|
func (m *Msg) From(f string) error {
|
||||||
m.SetHeader(HeaderTo, t...)
|
return m.SetAddrHeader(HeaderFrom, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cc sets the Cc: addresses of the Msg
|
// To takes and validates a given mail address list sets the To: addresses of the Msg
|
||||||
func (m *Msg) Cc(c ...string) {
|
func (m *Msg) To(t ...string) error {
|
||||||
m.SetHeader(HeaderCc, c...)
|
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
|
// SetMessageID generates a random message id for the mail
|
||||||
|
@ -73,16 +129,23 @@ func (m *Msg) SetMessageIDWithValue(v string) {
|
||||||
m.SetHeader(HeaderMessageID, v)
|
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
|
// automated mails like OOO replies
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc2076#section-3.9
|
// See: https://www.rfc-editor.org/rfc/rfc2076#section-3.9
|
||||||
func (m *Msg) SetBulk() {
|
func (m *Msg) SetBulk() {
|
||||||
m.SetHeader(HeaderPrecedence, "bulk")
|
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
|
// Header does something
|
||||||
// FIXME: This is only here to quickly show the set headers for debugging purpose. Remove me later
|
// FIXME: This is only here to quickly show the set headers for debugging purpose. Remove me later
|
||||||
func (m *Msg) Header() {
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue