diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index c1f67f4..c1f0110 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,10 +4,12 @@
-
+
+
+
@@ -94,7 +96,8 @@
-
+
+
true
diff --git a/README.md b/README.md
index 1109df4..dd3ff2d 100644
--- a/README.md
+++ b/README.md
@@ -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)
- }
}
```
\ No newline at end of file
diff --git a/client.go b/client.go
index 2b14b6a..bcf23af 100644
--- a/client.go
+++ b/client.go
@@ -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 {
diff --git a/cmd/main.go b/cmd/main.go
index 9da8ab7..932c549 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -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 `); 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)
diff --git a/header.go b/header.go
index b84651b..4a1475d 100644
--- a/header.go
+++ b/header.go
@@ -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"
)
diff --git a/mailmsg.go b/mailmsg.go
index 852ed94..19b7009 100644
--- a/mailmsg.go
+++ b/mailmsg.go
@@ -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)
}