diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index ed8b9b9..a1964bf 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,8 +4,13 @@
-
-
+
+
+
+
+
+
+
@@ -19,17 +24,22 @@
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
@@ -38,9 +48,7 @@
-
-
-
+
@@ -54,8 +62,7 @@
-
-
+
@@ -65,17 +72,6 @@
-
-
-
-
-
-
-
-
-
-
-
@@ -98,11 +94,8 @@
-
-
-
-
-
+
+
true
diff --git a/README.md b/README.md
index a95d818..1109df4 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,45 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/wneessen/go-mail)](https://goreportcard.com/report/github.com/wneessen/go-mail) [![Build Status](https://api.cirrus-ci.com/github/wneessen/go-mail.svg)](https://cirrus-ci.com/github/wneessen/go-mail)
-The main idea of this library is to provide a simple interface to sending mails for
-my [JS-Mailer](https://github.com/wneessen/js-mailer) project.
+The main idea of this library was to provide a simple interface to sending mails for
+my [JS-Mailer](https://github.com/wneessen/js-mailer) project. It quickly evolved into a
+full-fledged mail library.
-This library is **WIP** an should not be considered "production ready" before v1.0 is released.
\ No newline at end of file
+**This library is "WIP" an should not be considered "production ready", yet.**
+
+go-mail follows idiomatic Go style and best practice.
+
+## Example
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "github.com/wneessen/go-mail"
+ "os"
+ "time"
+)
+
+func main() {
+ c, err := mail.NewClient("mail.example.com", mail.WithTimeout(time.Millisecond*500),
+ mail.WithTLSPolicy(mail.TLSMandatory), mail.WithSMTPAuth(mail.SMTPAuthDigestMD5),
+ mail.WithUsername("tester@example.com"), mail.WithPassword("secureP4ssW0rd!"))
+ if err != nil {
+ fmt.Printf("failed to create new client: %s\n", err)
+ os.Exit(1)
+ }
+
+ ctx, cfn := context.WithCancel(context.Background())
+ defer cfn()
+
+ if err := c.DialWithContext(ctx); 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/auth.go b/auth.go
new file mode 100644
index 0000000..fb63b29
--- /dev/null
+++ b/auth.go
@@ -0,0 +1,36 @@
+package mail
+
+import "errors"
+
+// SMTPAuthType represents a string to any SMTP AUTH type
+type SMTPAuthType string
+
+// Supported SMTP AUTH types
+const (
+ // SMTPAuthLogin is the "LOGIN" SASL authentication mechanism
+ SMTPAuthLogin SMTPAuthType = "LOGIN"
+
+ // SMTPAuthPlain is the "PLAIN" authentication mechanism as described in RFC 4616
+ SMTPAuthPlain SMTPAuthType = "PLAIN"
+
+ // SMTPAuthCramMD5 is the "CRAM-MD5" SASL authentication mechanism as described in RFC 4954
+ SMTPAuthCramMD5 SMTPAuthType = "CRAM-MD5"
+
+ // SMTPAuthDigestMD5 is the "DIGEST-MD5" SASL authentication mechanism as described in RFC 4954
+ SMTPAuthDigestMD5 SMTPAuthType = "DIGEST-MD5"
+)
+
+// SMTP Auth related static errors
+var (
+ // ErrPlainAuthNotSupported should be used if the target server does not support the "PLAIN" schema
+ ErrPlainAuthNotSupported = errors.New("server does not support SMTP AUTH type: PLAIN")
+
+ // ErrLoginAuthNotSupported should be used if the target server does not support the "LOGIN" schema
+ ErrLoginAuthNotSupported = errors.New("server does not support SMTP AUTH type: LOGIN")
+
+ // ErrCramMD5AuthNotSupported should be used if the target server does not support the "CRAM-MD5" schema
+ ErrCramMD5AuthNotSupported = errors.New("server does not support SMTP AUTH type: CRAM-MD5")
+
+ // ErrDigestMD5AuthNotSupported should be used if the target server does not support the "DIGEST-MD5" schema
+ ErrDigestMD5AuthNotSupported = errors.New("server does not support SMTP AUTH type: DIGEST-MD5")
+)
diff --git a/client.go b/client.go
index cef5b18..1def948 100644
--- a/client.go
+++ b/client.go
@@ -8,6 +8,7 @@ import (
"net"
"net/smtp"
"os"
+ "strings"
"time"
)
@@ -43,6 +44,18 @@ type Client struct {
// enc indicates if a Client connection is encrypted or not
enc bool
+ // user is the SMTP AUTH username
+ user string
+
+ // pass is the corresponding SMTP AUTH password
+ pass string
+
+ // satype represents the authentication type for SMTP AUTH
+ satype SMTPAuthType
+
+ // smtpauth is a pointer to smtp.Auth
+ sa smtp.Auth
+
// The SMTP client that is set up when using the Dial*() methods
sc *smtp.Client
}
@@ -129,6 +142,34 @@ func WithTLSConfig(co *tls.Config) Option {
}
}
+// WithSMTPAuth tells the client to use the provided SMTPAuthType for authentication
+func WithSMTPAuth(t SMTPAuthType) Option {
+ return func(c *Client) {
+ c.satype = t
+ }
+}
+
+// WithSMTPAuthCustom tells the client to use the provided smtp.Auth for SMTP authentication
+func WithSMTPAuthCustom(a smtp.Auth) Option {
+ return func(c *Client) {
+ c.sa = a
+ }
+}
+
+// WithUsername tells the client to use the provided string as username for authentication
+func WithUsername(u string) Option {
+ return func(c *Client) {
+ c.user = u
+ }
+}
+
+// WithPassword tells the client to use the provided string as password/secret for authentication
+func WithPassword(p string) Option {
+ return func(c *Client) {
+ c.pass = p
+ }
+}
+
// TLSPolicy returns the currently set TLSPolicy as string
func (c *Client) TLSPolicy() string {
return c.tlspolicy.String()
@@ -226,5 +267,51 @@ func (c *Client) DialWithContext(uctx context.Context) error {
_, c.enc = c.sc.TLSConnectionState()
}
+ if err := c.auth(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// auth will try to perform SMTP AUTH if requested
+func (c *Client) auth() error {
+ if c.sa == nil && c.satype != "" {
+ sa, sat := c.sc.Extension("AUTH")
+ if !sa {
+ return fmt.Errorf("server does not support SMTP AUTH")
+ }
+
+ switch c.satype {
+ case SMTPAuthPlain:
+ if !strings.Contains(sat, string(SMTPAuthPlain)) {
+ return ErrPlainAuthNotSupported
+ }
+ c.sa = smtp.PlainAuth("", c.user, c.pass, c.host)
+ case SMTPAuthLogin:
+ if !strings.Contains(sat, string(SMTPAuthLogin)) {
+ return ErrLoginAuthNotSupported
+ }
+ c.sa = smtp.PlainAuth("", c.user, c.pass, c.host)
+ case SMTPAuthCramMD5:
+ if !strings.Contains(sat, string(SMTPAuthCramMD5)) {
+ return ErrCramMD5AuthNotSupported
+ }
+ c.sa = smtp.CRAMMD5Auth(c.user, c.pass)
+ case SMTPAuthDigestMD5:
+ if !strings.Contains(sat, string(SMTPAuthDigestMD5)) {
+ return ErrDigestMD5AuthNotSupported
+ }
+ c.sa = smtp.CRAMMD5Auth(c.user, c.pass)
+ default:
+ return fmt.Errorf("unsupported SMTP AUTH type %q", c.satype)
+ }
+ }
+
+ if c.sa != nil {
+ if err := c.sc.Auth(c.sa); err != nil {
+ return fmt.Errorf("SMTP AUTH failed: %w", err)
+ }
+ }
return nil
}
diff --git a/cmd/main.go b/cmd/main.go
index c7e4f12..b2aadce 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -14,7 +14,11 @@ func main() {
fmt.Printf("$TEST_HOST env variable cannot be empty\n")
os.Exit(1)
}
- c, err := mail.NewClient(th, mail.WithTimeout(time.Millisecond*500))
+
+ tu := os.Getenv("TEST_USER")
+ tp := os.Getenv("TEST_PASS")
+ 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)