mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-08 23:12:54 +01:00
191 lines
4.2 KiB
Go
191 lines
4.2 KiB
Go
package mail
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/smtp"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// DefaultPort is the default connection port cto the SMTP server
|
|
const DefaultPort = 25
|
|
|
|
// DefaultTimeout is the default connection timeout
|
|
const DefaultTimeout = time.Second * 30
|
|
|
|
// Client is the SMTP client struct
|
|
type Client struct {
|
|
// Hostname of the target SMTP server cto connect cto
|
|
host string
|
|
|
|
// Port of the SMTP server cto connect cto
|
|
port int
|
|
|
|
// Use SSL for the connection
|
|
ssl bool
|
|
|
|
// tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol
|
|
tlspolicy TLSPolicy
|
|
|
|
// tlsconfig represents the tls.Config setting for the STARTTLS connection
|
|
tlsconfig *tls.Config
|
|
|
|
// Timeout for the SMTP server connection
|
|
cto time.Duration
|
|
|
|
// HELO/EHLO string for the greeting the target SMTP server
|
|
helo string
|
|
|
|
// The SMTP client that is set up when using the Dial*() methods
|
|
sc *smtp.Client
|
|
}
|
|
|
|
// Option returns a function that can be used for grouping Client options
|
|
type Option func(*Client)
|
|
|
|
var (
|
|
// ErrNoHostname should be used if a Client has no hostname set
|
|
ErrNoHostname = errors.New("hostname for client cannot be empty")
|
|
|
|
// ErrNoSTARTTLS should be used if the target server does not support the STARTTLS protocol
|
|
ErrNoSTARTTLS = errors.New("target host does not support STARTTLS")
|
|
)
|
|
|
|
// NewClient returns a new Session client object
|
|
func NewClient(h string, o ...Option) (*Client, error) {
|
|
c := &Client{
|
|
host: h,
|
|
port: DefaultPort,
|
|
cto: DefaultTimeout,
|
|
tlspolicy: TLSMandatory,
|
|
tlsconfig: &tls.Config{ServerName: h},
|
|
}
|
|
|
|
// Set default HELO/EHLO hostname
|
|
if err := c.setDefaultHelo(); err != nil {
|
|
return c, err
|
|
}
|
|
|
|
// Override defaults with optionally provided Option functions
|
|
for _, co := range o {
|
|
if co == nil {
|
|
continue
|
|
}
|
|
co(c)
|
|
}
|
|
|
|
// Some settings in a Client cannot be empty/unset
|
|
if c.host == "" {
|
|
return c, ErrNoHostname
|
|
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// WithPort overrides the default connection port
|
|
func WithPort(p int) Option {
|
|
return func(c *Client) {
|
|
c.port = p
|
|
}
|
|
}
|
|
|
|
// WithTimeout overrides the default connection timeout
|
|
func WithTimeout(t time.Duration) Option {
|
|
return func(c *Client) {
|
|
c.cto = t
|
|
}
|
|
}
|
|
|
|
// WithSSL tells the client to use a SSL/TLS connection
|
|
func WithSSL() Option {
|
|
return func(c *Client) {
|
|
c.ssl = true
|
|
}
|
|
}
|
|
|
|
// WithHELO tells the client to use the provided string as HELO/EHLO greeting host
|
|
func WithHELO(h string) Option {
|
|
return func(c *Client) {
|
|
c.helo = h
|
|
}
|
|
}
|
|
|
|
// TLSPolicy returns the currently set TLSPolicy as string
|
|
func (c *Client) TLSPolicy() string {
|
|
return fmt.Sprintf("%s", c.tlspolicy)
|
|
}
|
|
|
|
// SetTLSPolicy overrides the current TLSPolicy with the given TLSPolicy value
|
|
func (c *Client) SetTLSPolicy(p TLSPolicy) {
|
|
c.tlspolicy = p
|
|
}
|
|
|
|
// Send sends out the mail message
|
|
func (c *Client) Send() error {
|
|
return nil
|
|
}
|
|
|
|
// 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()
|
|
if err != nil {
|
|
return fmt.Errorf("failed cto read local hostname: %w", err)
|
|
}
|
|
c.helo = hn
|
|
return nil
|
|
}
|
|
|
|
// Dial establishes a connection cto the SMTP server with a default context.Background
|
|
func (c *Client) Dial() error {
|
|
ctx := context.Background()
|
|
return c.DialWithContext(ctx)
|
|
}
|
|
|
|
// DialWithContext establishes a connection cto the SMTP server with a given context.Context
|
|
func (c *Client) DialWithContext(uctx context.Context) error {
|
|
ctx, cfn := context.WithTimeout(uctx, c.cto)
|
|
defer cfn()
|
|
|
|
nd := net.Dialer{}
|
|
td := tls.Dialer{}
|
|
var co net.Conn
|
|
var err error
|
|
if c.ssl {
|
|
co, err = td.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", c.host, c.port))
|
|
}
|
|
if !c.ssl {
|
|
co, err = nd.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", c.host, c.port))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.sc, err = smtp.NewClient(co, c.host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := c.sc.Hello(c.helo); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !c.ssl && c.tlspolicy != NoTLS {
|
|
if ok, _ := c.sc.Extension("STARTTLS"); !ok {
|
|
return ErrNoSTARTTLS
|
|
}
|
|
if err := c.sc.StartTLS(c.tlsconfig); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|