mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 13:50:49 +01:00
Compare commits
4 commits
4e880ab31c
...
e1098091c3
Author | SHA1 | Date | |
---|---|---|---|
e1098091c3 | |||
12a145f612 | |||
d81dee95a8 | |||
c455b45a3e |
7 changed files with 417 additions and 403 deletions
|
@ -20,39 +20,39 @@ type Base64LineBreaker struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
var nl = []byte(SingleNewLine)
|
var newlineBytes = []byte(SingleNewLine)
|
||||||
|
|
||||||
// Write writes the data stream and inserts a SingleNewLine when the maximum
|
// Write writes the data stream and inserts a SingleNewLine when the maximum
|
||||||
// line length is reached
|
// line length is reached
|
||||||
func (l *Base64LineBreaker) Write(b []byte) (n int, err error) {
|
func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) {
|
||||||
if l.out == nil {
|
if l.out == nil {
|
||||||
err = fmt.Errorf(ErrNoOutWriter)
|
err = fmt.Errorf(ErrNoOutWriter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if l.used+len(b) < MaxBodyLength {
|
if l.used+len(data) < MaxBodyLength {
|
||||||
copy(l.line[l.used:], b)
|
copy(l.line[l.used:], data)
|
||||||
l.used += len(b)
|
l.used += len(data)
|
||||||
return len(b), nil
|
return len(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = l.out.Write(l.line[0:l.used])
|
numBytes, err = l.out.Write(l.line[0:l.used])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
excess := MaxBodyLength - l.used
|
excess := MaxBodyLength - l.used
|
||||||
l.used = 0
|
l.used = 0
|
||||||
|
|
||||||
n, err = l.out.Write(b[0:excess])
|
numBytes, err = l.out.Write(data[0:excess])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = l.out.Write(nl)
|
numBytes, err = l.out.Write(newlineBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return l.Write(b[excess:])
|
return l.Write(data[excess:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the Base64LineBreaker and writes any access data that is still
|
// Close closes the Base64LineBreaker and writes any access data that is still
|
||||||
|
@ -63,7 +63,7 @@ func (l *Base64LineBreaker) Close() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = l.out.Write(nl)
|
_, err = l.out.Write(newlineBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
284
client.go
284
client.go
|
@ -87,11 +87,11 @@ type DialContextFunc func(ctx context.Context, network, address string) (net.Con
|
||||||
|
|
||||||
// Client is the SMTP client struct
|
// Client is the SMTP client struct
|
||||||
type Client struct {
|
type Client struct {
|
||||||
// co is the net.Conn that the smtp.Client is based on
|
// connection is the net.Conn that the smtp.Client is based on
|
||||||
co net.Conn
|
connection net.Conn
|
||||||
|
|
||||||
// Timeout for the SMTP server connection
|
// Timeout for the SMTP server connection
|
||||||
cto time.Duration
|
connTimeout time.Duration
|
||||||
|
|
||||||
// dsn indicates that we want to use DSN for the Client
|
// dsn indicates that we want to use DSN for the Client
|
||||||
dsn bool
|
dsn bool
|
||||||
|
@ -102,8 +102,8 @@ type Client struct {
|
||||||
// dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled
|
// dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled
|
||||||
dsnrntype []string
|
dsnrntype []string
|
||||||
|
|
||||||
// enc indicates if a Client connection is encrypted or not
|
// isEncrypted indicates if a Client connection is encrypted or not
|
||||||
enc bool
|
isEncrypted bool
|
||||||
|
|
||||||
// noNoop indicates the Noop is to be skipped
|
// noNoop indicates the Noop is to be skipped
|
||||||
noNoop bool
|
noNoop bool
|
||||||
|
@ -121,17 +121,17 @@ type Client struct {
|
||||||
port int
|
port int
|
||||||
fallbackPort int
|
fallbackPort int
|
||||||
|
|
||||||
// sa is a pointer to smtp.Auth
|
// smtpAuth is a pointer to smtp.Auth
|
||||||
sa smtp.Auth
|
smtpAuth smtp.Auth
|
||||||
|
|
||||||
// satype represents the authentication type for SMTP AUTH
|
// smtpAuthType represents the authentication type for SMTP AUTH
|
||||||
satype SMTPAuthType
|
smtpAuthType SMTPAuthType
|
||||||
|
|
||||||
// sc is the smtp.Client that is set up when using the Dial*() methods
|
// smtpClient is the smtp.Client that is set up when using the Dial*() methods
|
||||||
sc *smtp.Client
|
smtpClient *smtp.Client
|
||||||
|
|
||||||
// Use SSL for the connection
|
// Use SSL for the connection
|
||||||
ssl bool
|
useSSL bool
|
||||||
|
|
||||||
// tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol
|
// tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol
|
||||||
tlspolicy TLSPolicy
|
tlspolicy TLSPolicy
|
||||||
|
@ -142,11 +142,11 @@ type Client struct {
|
||||||
// user is the SMTP AUTH username
|
// user is the SMTP AUTH username
|
||||||
user string
|
user string
|
||||||
|
|
||||||
// dl enables the debug logging on the SMTP client
|
// useDebugLog enables the debug logging on the SMTP client
|
||||||
dl bool
|
useDebugLog bool
|
||||||
|
|
||||||
// l is a logger that implements the log.Logger interface
|
// logger is a logger that implements the log.Logger interface
|
||||||
l log.Logger
|
logger log.Logger
|
||||||
|
|
||||||
// dialContextFunc is a custom DialContext function to dial target SMTP server
|
// dialContextFunc is a custom DialContext function to dial target SMTP server
|
||||||
dialContextFunc DialContextFunc
|
dialContextFunc DialContextFunc
|
||||||
|
@ -198,13 +198,13 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient returns a new Session client object
|
// NewClient returns a new Session client object
|
||||||
func NewClient(h string, o ...Option) (*Client, error) {
|
func NewClient(host string, opts ...Option) (*Client, error) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
cto: DefaultTimeout,
|
connTimeout: DefaultTimeout,
|
||||||
host: h,
|
host: host,
|
||||||
port: DefaultPort,
|
port: DefaultPort,
|
||||||
tlsconfig: &tls.Config{ServerName: h, MinVersion: DefaultTLSMinVersion},
|
tlsconfig: &tls.Config{ServerName: host, MinVersion: DefaultTLSMinVersion},
|
||||||
tlspolicy: DefaultTLSPolicy,
|
tlspolicy: DefaultTLSPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default HELO/EHLO hostname
|
// Set default HELO/EHLO hostname
|
||||||
|
@ -213,11 +213,11 @@ func NewClient(h string, o ...Option) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override defaults with optionally provided Option functions
|
// Override defaults with optionally provided Option functions
|
||||||
for _, co := range o {
|
for _, opt := range opts {
|
||||||
if co == nil {
|
if opt == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := co(c); err != nil {
|
if err := opt(c); err != nil {
|
||||||
return c, fmt.Errorf("failed to apply option: %w", err)
|
return c, fmt.Errorf("failed to apply option: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,23 +231,23 @@ func NewClient(h string, o ...Option) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPort overrides the default connection port
|
// WithPort overrides the default connection port
|
||||||
func WithPort(p int) Option {
|
func WithPort(port int) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if p < 1 || p > 65535 {
|
if port < 1 || port > 65535 {
|
||||||
return ErrInvalidPort
|
return ErrInvalidPort
|
||||||
}
|
}
|
||||||
c.port = p
|
c.port = port
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTimeout overrides the default connection timeout
|
// WithTimeout overrides the default connection timeout
|
||||||
func WithTimeout(t time.Duration) Option {
|
func WithTimeout(timeout time.Duration) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if t <= 0 {
|
if timeout <= 0 {
|
||||||
return ErrInvalidTimeout
|
return ErrInvalidTimeout
|
||||||
}
|
}
|
||||||
c.cto = t
|
c.connTimeout = timeout
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ func WithTimeout(t time.Duration) Option {
|
||||||
// Deprecated: use WithSSLPort instead.
|
// Deprecated: use WithSSLPort instead.
|
||||||
func WithSSL() Option {
|
func WithSSL() Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.ssl = true
|
c.useSSL = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,9 +267,9 @@ func WithSSL() Option {
|
||||||
//
|
//
|
||||||
// When the SSL connection fails and fallback is set to true,
|
// When the SSL connection fails and fallback is set to true,
|
||||||
// the client will attempt to connect on port 25 using plaintext.
|
// the client will attempt to connect on port 25 using plaintext.
|
||||||
func WithSSLPort(fb bool) Option {
|
func WithSSLPort(fallback bool) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.SetSSLPort(true, fb)
|
c.SetSSLPort(true, fallback)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,26 +278,26 @@ func WithSSLPort(fb bool) Option {
|
||||||
// to StdErr
|
// to StdErr
|
||||||
func WithDebugLog() Option {
|
func WithDebugLog() Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.dl = true
|
c.useDebugLog = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLogger overrides the default log.Logger that is used for debug logging
|
// WithLogger overrides the default log.Logger that is used for debug logging
|
||||||
func WithLogger(l log.Logger) Option {
|
func WithLogger(logger log.Logger) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.l = l
|
c.logger = logger
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHELO tells the client to use the provided string as HELO/EHLO greeting host
|
// WithHELO tells the client to use the provided string as HELO/EHLO greeting host
|
||||||
func WithHELO(h string) Option {
|
func WithHELO(helo string) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if h == "" {
|
if helo == "" {
|
||||||
return ErrInvalidHELO
|
return ErrInvalidHELO
|
||||||
}
|
}
|
||||||
c.helo = h
|
c.helo = helo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,9 +305,9 @@ func WithHELO(h string) Option {
|
||||||
// WithTLSPolicy tells the client to use the provided TLSPolicy
|
// WithTLSPolicy tells the client to use the provided TLSPolicy
|
||||||
//
|
//
|
||||||
// Deprecated: use WithTLSPortPolicy instead.
|
// Deprecated: use WithTLSPortPolicy instead.
|
||||||
func WithTLSPolicy(p TLSPolicy) Option {
|
func WithTLSPolicy(policy TLSPolicy) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.tlspolicy = p
|
c.tlspolicy = policy
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,52 +319,52 @@ func WithTLSPolicy(p TLSPolicy) Option {
|
||||||
// If the connection fails with TLSOpportunistic,
|
// If the connection fails with TLSOpportunistic,
|
||||||
// a plaintext connection is attempted on port 25 as a fallback.
|
// a plaintext connection is attempted on port 25 as a fallback.
|
||||||
// NoTLS will allways use port 25.
|
// NoTLS will allways use port 25.
|
||||||
func WithTLSPortPolicy(p TLSPolicy) Option {
|
func WithTLSPortPolicy(policy TLSPolicy) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.SetTLSPortPolicy(p)
|
c.SetTLSPortPolicy(policy)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTLSConfig tells the client to use the provided *tls.Config
|
// WithTLSConfig tells the client to use the provided *tls.Config
|
||||||
func WithTLSConfig(co *tls.Config) Option {
|
func WithTLSConfig(tlsconfig *tls.Config) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if co == nil {
|
if tlsconfig == nil {
|
||||||
return ErrInvalidTLSConfig
|
return ErrInvalidTLSConfig
|
||||||
}
|
}
|
||||||
c.tlsconfig = co
|
c.tlsconfig = tlsconfig
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSMTPAuth tells the client to use the provided SMTPAuthType for authentication
|
// WithSMTPAuth tells the client to use the provided SMTPAuthType for authentication
|
||||||
func WithSMTPAuth(t SMTPAuthType) Option {
|
func WithSMTPAuth(authtype SMTPAuthType) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.satype = t
|
c.smtpAuthType = authtype
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSMTPAuthCustom tells the client to use the provided smtp.Auth for SMTP authentication
|
// WithSMTPAuthCustom tells the client to use the provided smtp.Auth for SMTP authentication
|
||||||
func WithSMTPAuthCustom(a smtp.Auth) Option {
|
func WithSMTPAuthCustom(smtpAuth smtp.Auth) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.sa = a
|
c.smtpAuth = smtpAuth
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithUsername tells the client to use the provided string as username for authentication
|
// WithUsername tells the client to use the provided string as username for authentication
|
||||||
func WithUsername(u string) Option {
|
func WithUsername(username string) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.user = u
|
c.user = username
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPassword tells the client to use the provided string as password/secret for authentication
|
// WithPassword tells the client to use the provided string as password/secret for authentication
|
||||||
func WithPassword(p string) Option {
|
func WithPassword(password string) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.pass = p
|
c.pass = password
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,9 +386,9 @@ func WithDSN() Option {
|
||||||
// as described in the RFC 1891 and set the MAIL FROM Return option type to the
|
// as described in the RFC 1891 and set the MAIL FROM Return option type to the
|
||||||
// given DSNMailReturnOption
|
// given DSNMailReturnOption
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc1891
|
// See: https://www.rfc-editor.org/rfc/rfc1891
|
||||||
func WithDSNMailReturnType(mro DSNMailReturnOption) Option {
|
func WithDSNMailReturnType(option DSNMailReturnOption) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
switch mro {
|
switch option {
|
||||||
case DSNMailReturnHeadersOnly:
|
case DSNMailReturnHeadersOnly:
|
||||||
case DSNMailReturnFull:
|
case DSNMailReturnFull:
|
||||||
default:
|
default:
|
||||||
|
@ -396,7 +396,7 @@ func WithDSNMailReturnType(mro DSNMailReturnOption) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.dsn = true
|
c.dsn = true
|
||||||
c.dsnmrtype = mro
|
c.dsnmrtype = option
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,13 +404,13 @@ func WithDSNMailReturnType(mro DSNMailReturnOption) Option {
|
||||||
// WithDSNRcptNotifyType enables the Client to request DSNs as described in the RFC 1891
|
// WithDSNRcptNotifyType enables the Client to request DSNs as described in the RFC 1891
|
||||||
// and sets the RCPT TO notify options to the given list of DSNRcptNotifyOption
|
// and sets the RCPT TO notify options to the given list of DSNRcptNotifyOption
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc1891
|
// See: https://www.rfc-editor.org/rfc/rfc1891
|
||||||
func WithDSNRcptNotifyType(rno ...DSNRcptNotifyOption) Option {
|
func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
var rnol []string
|
var rcptOpts []string
|
||||||
var ns, nns bool
|
var ns, nns bool
|
||||||
if len(rno) > 0 {
|
if len(opts) > 0 {
|
||||||
for _, crno := range rno {
|
for _, opt := range opts {
|
||||||
switch crno {
|
switch opt {
|
||||||
case DSNRcptNotifyNever:
|
case DSNRcptNotifyNever:
|
||||||
ns = true
|
ns = true
|
||||||
case DSNRcptNotifySuccess:
|
case DSNRcptNotifySuccess:
|
||||||
|
@ -422,7 +422,7 @@ func WithDSNRcptNotifyType(rno ...DSNRcptNotifyOption) Option {
|
||||||
default:
|
default:
|
||||||
return ErrInvalidDSNRcptNotifyOption
|
return ErrInvalidDSNRcptNotifyOption
|
||||||
}
|
}
|
||||||
rnol = append(rnol, string(crno))
|
rcptOpts = append(rcptOpts, string(opt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ns && nns {
|
if ns && nns {
|
||||||
|
@ -430,7 +430,7 @@ func WithDSNRcptNotifyType(rno ...DSNRcptNotifyOption) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.dsn = true
|
c.dsn = true
|
||||||
c.dsnrntype = rnol
|
c.dsnrntype = rcptOpts
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -445,9 +445,9 @@ func WithoutNoop() Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithDialContextFunc overrides the default DialContext for connecting SMTP server
|
// WithDialContextFunc overrides the default DialContext for connecting SMTP server
|
||||||
func WithDialContextFunc(f DialContextFunc) Option {
|
func WithDialContextFunc(dialCtxFunc DialContextFunc) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.dialContextFunc = f
|
c.dialContextFunc = dialCtxFunc
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -463,8 +463,8 @@ func (c *Client) ServerAddr() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTLSPolicy overrides the current TLSPolicy with the given TLSPolicy value
|
// SetTLSPolicy overrides the current TLSPolicy with the given TLSPolicy value
|
||||||
func (c *Client) SetTLSPolicy(p TLSPolicy) {
|
func (c *Client) SetTLSPolicy(policy TLSPolicy) {
|
||||||
c.tlspolicy = p
|
c.tlspolicy = policy
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTLSPortPolicy overrides the current TLSPolicy with the given TLSPolicy
|
// SetTLSPortPolicy overrides the current TLSPolicy with the given TLSPolicy
|
||||||
|
@ -474,22 +474,22 @@ func (c *Client) SetTLSPolicy(p TLSPolicy) {
|
||||||
// If the connection fails with TLSOpportunistic, a plaintext connection is
|
// If the connection fails with TLSOpportunistic, a plaintext connection is
|
||||||
// attempted on port 25 as a fallback.
|
// attempted on port 25 as a fallback.
|
||||||
// NoTLS will allways use port 25.
|
// NoTLS will allways use port 25.
|
||||||
func (c *Client) SetTLSPortPolicy(p TLSPolicy) {
|
func (c *Client) SetTLSPortPolicy(policy TLSPolicy) {
|
||||||
c.port = DefaultPortTLS
|
c.port = DefaultPortTLS
|
||||||
|
|
||||||
if p == TLSOpportunistic {
|
if policy == TLSOpportunistic {
|
||||||
c.fallbackPort = DefaultPort
|
c.fallbackPort = DefaultPort
|
||||||
}
|
}
|
||||||
if p == NoTLS {
|
if policy == NoTLS {
|
||||||
c.port = DefaultPort
|
c.port = DefaultPort
|
||||||
}
|
}
|
||||||
|
|
||||||
c.tlspolicy = p
|
c.tlspolicy = policy
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSSL tells the Client wether to use SSL or not
|
// SetSSL tells the Client wether to use SSL or not
|
||||||
func (c *Client) SetSSL(s bool) {
|
func (c *Client) SetSSL(ssl bool) {
|
||||||
c.ssl = s
|
c.useSSL = ssl
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSSLPort tells the Client wether or not to use SSL and fallback.
|
// SetSSLPort tells the Client wether or not to use SSL and fallback.
|
||||||
|
@ -499,124 +499,124 @@ func (c *Client) SetSSL(s bool) {
|
||||||
// Port 25 is used when SSL is unset (false).
|
// Port 25 is used when SSL is unset (false).
|
||||||
// When the SSL connection fails and fb is set to true,
|
// When the SSL connection fails and fb is set to true,
|
||||||
// the client will attempt to connect on port 25 using plaintext.
|
// the client will attempt to connect on port 25 using plaintext.
|
||||||
func (c *Client) SetSSLPort(ssl bool, fb bool) {
|
func (c *Client) SetSSLPort(ssl bool, fallback bool) {
|
||||||
c.port = DefaultPort
|
c.port = DefaultPort
|
||||||
if ssl {
|
if ssl {
|
||||||
c.port = DefaultPortSSL
|
c.port = DefaultPortSSL
|
||||||
}
|
}
|
||||||
|
|
||||||
c.fallbackPort = 0
|
c.fallbackPort = 0
|
||||||
if fb {
|
if fallback {
|
||||||
c.fallbackPort = DefaultPort
|
c.fallbackPort = DefaultPort
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ssl = ssl
|
c.useSSL = ssl
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDebugLog tells the Client whether debug logging is enabled or not
|
// SetDebugLog tells the Client whether debug logging is enabled or not
|
||||||
func (c *Client) SetDebugLog(v bool) {
|
func (c *Client) SetDebugLog(val bool) {
|
||||||
c.dl = v
|
c.useDebugLog = val
|
||||||
if c.sc != nil {
|
if c.smtpClient != nil {
|
||||||
c.sc.SetDebugLog(v)
|
c.smtpClient.SetDebugLog(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogger tells the Client which log.Logger to use
|
// SetLogger tells the Client which log.Logger to use
|
||||||
func (c *Client) SetLogger(l log.Logger) {
|
func (c *Client) SetLogger(logger log.Logger) {
|
||||||
c.l = l
|
c.logger = logger
|
||||||
if c.sc != nil {
|
if c.smtpClient != nil {
|
||||||
c.sc.SetLogger(l)
|
c.smtpClient.SetLogger(logger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTLSConfig overrides the current *tls.Config with the given *tls.Config value
|
// SetTLSConfig overrides the current *tls.Config with the given *tls.Config value
|
||||||
func (c *Client) SetTLSConfig(co *tls.Config) error {
|
func (c *Client) SetTLSConfig(tlsconfig *tls.Config) error {
|
||||||
if co == nil {
|
if tlsconfig == nil {
|
||||||
return ErrInvalidTLSConfig
|
return ErrInvalidTLSConfig
|
||||||
}
|
}
|
||||||
c.tlsconfig = co
|
c.tlsconfig = tlsconfig
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUsername overrides the current username string with the given value
|
// SetUsername overrides the current username string with the given value
|
||||||
func (c *Client) SetUsername(u string) {
|
func (c *Client) SetUsername(username string) {
|
||||||
c.user = u
|
c.user = username
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPassword overrides the current password string with the given value
|
// SetPassword overrides the current password string with the given value
|
||||||
func (c *Client) SetPassword(p string) {
|
func (c *Client) SetPassword(password string) {
|
||||||
c.pass = p
|
c.pass = password
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSMTPAuth overrides the current SMTP AUTH type setting with the given value
|
// SetSMTPAuth overrides the current SMTP AUTH type setting with the given value
|
||||||
func (c *Client) SetSMTPAuth(a SMTPAuthType) {
|
func (c *Client) SetSMTPAuth(authtype SMTPAuthType) {
|
||||||
c.satype = a
|
c.smtpAuthType = authtype
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSMTPAuthCustom overrides the current SMTP AUTH setting with the given custom smtp.Auth
|
// SetSMTPAuthCustom overrides the current SMTP AUTH setting with the given custom smtp.Auth
|
||||||
func (c *Client) SetSMTPAuthCustom(sa smtp.Auth) {
|
func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) {
|
||||||
c.sa = sa
|
c.smtpAuth = smtpAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
hostname, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed cto read local hostname: %w", err)
|
return fmt.Errorf("failed to read local hostname: %w", err)
|
||||||
}
|
}
|
||||||
c.helo = hn
|
c.helo = hostname
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialWithContext establishes a connection cto the SMTP server with a given context.Context
|
// DialWithContext establishes a connection to the SMTP server with a given context.Context
|
||||||
func (c *Client) DialWithContext(pc context.Context) error {
|
func (c *Client) DialWithContext(dialCtx context.Context) error {
|
||||||
ctx, cfn := context.WithDeadline(pc, time.Now().Add(c.cto))
|
ctx, cancel := context.WithDeadline(dialCtx, time.Now().Add(c.connTimeout))
|
||||||
defer cfn()
|
defer cancel()
|
||||||
|
|
||||||
if c.dialContextFunc == nil {
|
if c.dialContextFunc == nil {
|
||||||
nd := net.Dialer{}
|
netDialer := net.Dialer{}
|
||||||
c.dialContextFunc = nd.DialContext
|
c.dialContextFunc = netDialer.DialContext
|
||||||
|
|
||||||
if c.ssl {
|
if c.useSSL {
|
||||||
td := tls.Dialer{NetDialer: &nd, Config: c.tlsconfig}
|
tlsDialer := tls.Dialer{NetDialer: &netDialer, Config: c.tlsconfig}
|
||||||
c.enc = true
|
c.isEncrypted = true
|
||||||
c.dialContextFunc = td.DialContext
|
c.dialContextFunc = tlsDialer.DialContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
c.co, err = c.dialContextFunc(ctx, "tcp", c.ServerAddr())
|
c.connection, err = c.dialContextFunc(ctx, "tcp", c.ServerAddr())
|
||||||
if err != nil && c.fallbackPort != 0 {
|
if err != nil && c.fallbackPort != 0 {
|
||||||
// TODO: should we somehow log or append the previous error?
|
// TODO: should we somehow log or append the previous error?
|
||||||
c.co, err = c.dialContextFunc(ctx, "tcp", c.serverFallbackAddr())
|
c.connection, err = c.dialContextFunc(ctx, "tcp", c.serverFallbackAddr())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sc, err := smtp.NewClient(c.co, c.host)
|
client, err := smtp.NewClient(c.connection, c.host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if sc == nil {
|
if client == nil {
|
||||||
return fmt.Errorf("SMTP client is nil")
|
return fmt.Errorf("SMTP client is nil")
|
||||||
}
|
}
|
||||||
c.sc = sc
|
c.smtpClient = client
|
||||||
|
|
||||||
if c.l != nil {
|
if c.logger != nil {
|
||||||
c.sc.SetLogger(c.l)
|
c.smtpClient.SetLogger(c.logger)
|
||||||
}
|
}
|
||||||
if c.dl {
|
if c.useDebugLog {
|
||||||
c.sc.SetDebugLog(true)
|
c.smtpClient.SetDebugLog(true)
|
||||||
}
|
}
|
||||||
if err := c.sc.Hello(c.helo); err != nil {
|
if err = c.smtpClient.Hello(c.helo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.tls(); err != nil {
|
if err = c.tls(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.auth(); err != nil {
|
if err = c.auth(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,7 +628,7 @@ func (c *Client) Close() error {
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.sc.Quit(); err != nil {
|
if err := c.smtpClient.Quit(); err != nil {
|
||||||
return fmt.Errorf("failed to close SMTP client: %w", err)
|
return fmt.Errorf("failed to close SMTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,7 +640,7 @@ func (c *Client) Reset() error {
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.sc.Reset(); err != nil {
|
if err := c.smtpClient.Reset(); err != nil {
|
||||||
return fmt.Errorf("failed to send RSET to SMTP client: %w", err)
|
return fmt.Errorf("failed to send RSET to SMTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,17 +672,17 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, ml ...*Msg) error {
|
||||||
// checkConn makes sure that a required server connection is available and extends the
|
// checkConn makes sure that a required server connection is available and extends the
|
||||||
// connection deadline
|
// connection deadline
|
||||||
func (c *Client) checkConn() error {
|
func (c *Client) checkConn() error {
|
||||||
if c.co == nil {
|
if c.connection == nil {
|
||||||
return ErrNoActiveConnection
|
return ErrNoActiveConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.noNoop {
|
if !c.noNoop {
|
||||||
if err := c.sc.Noop(); err != nil {
|
if err := c.smtpClient.Noop(); err != nil {
|
||||||
return ErrNoActiveConnection
|
return ErrNoActiveConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.co.SetDeadline(time.Now().Add(c.cto)); err != nil {
|
if err := c.connection.SetDeadline(time.Now().Add(c.connTimeout)); err != nil {
|
||||||
return ErrDeadlineExtendFailed
|
return ErrDeadlineExtendFailed
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -696,12 +696,12 @@ func (c *Client) serverFallbackAddr() string {
|
||||||
|
|
||||||
// tls tries to make sure that the STARTTLS requirements are satisfied
|
// tls tries to make sure that the STARTTLS requirements are satisfied
|
||||||
func (c *Client) tls() error {
|
func (c *Client) tls() error {
|
||||||
if c.co == nil {
|
if c.connection == nil {
|
||||||
return ErrNoActiveConnection
|
return ErrNoActiveConnection
|
||||||
}
|
}
|
||||||
if !c.ssl && c.tlspolicy != NoTLS {
|
if !c.useSSL && c.tlspolicy != NoTLS {
|
||||||
est := false
|
est := false
|
||||||
st, _ := c.sc.Extension("STARTTLS")
|
st, _ := c.smtpClient.Extension("STARTTLS")
|
||||||
if c.tlspolicy == TLSMandatory {
|
if c.tlspolicy == TLSMandatory {
|
||||||
est = true
|
est = true
|
||||||
if !st {
|
if !st {
|
||||||
|
@ -715,11 +715,11 @@ func (c *Client) tls() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if est {
|
if est {
|
||||||
if err := c.sc.StartTLS(c.tlsconfig); err != nil {
|
if err := c.smtpClient.StartTLS(c.tlsconfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, c.enc = c.sc.TLSConnectionState()
|
_, c.isEncrypted = c.smtpClient.TLSConnectionState()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -729,40 +729,40 @@ func (c *Client) auth() error {
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
return fmt.Errorf("failed to authenticate: %w", err)
|
return fmt.Errorf("failed to authenticate: %w", err)
|
||||||
}
|
}
|
||||||
if c.sa == nil && c.satype != "" {
|
if c.smtpAuth == nil && c.smtpAuthType != "" {
|
||||||
sa, sat := c.sc.Extension("AUTH")
|
sa, sat := c.smtpClient.Extension("AUTH")
|
||||||
if !sa {
|
if !sa {
|
||||||
return fmt.Errorf("server does not support SMTP AUTH")
|
return fmt.Errorf("server does not support SMTP AUTH")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch c.satype {
|
switch c.smtpAuthType {
|
||||||
case SMTPAuthPlain:
|
case SMTPAuthPlain:
|
||||||
if !strings.Contains(sat, string(SMTPAuthPlain)) {
|
if !strings.Contains(sat, string(SMTPAuthPlain)) {
|
||||||
return ErrPlainAuthNotSupported
|
return ErrPlainAuthNotSupported
|
||||||
}
|
}
|
||||||
c.sa = smtp.PlainAuth("", c.user, c.pass, c.host)
|
c.smtpAuth = smtp.PlainAuth("", c.user, c.pass, c.host)
|
||||||
case SMTPAuthLogin:
|
case SMTPAuthLogin:
|
||||||
if !strings.Contains(sat, string(SMTPAuthLogin)) {
|
if !strings.Contains(sat, string(SMTPAuthLogin)) {
|
||||||
return ErrLoginAuthNotSupported
|
return ErrLoginAuthNotSupported
|
||||||
}
|
}
|
||||||
c.sa = smtp.LoginAuth(c.user, c.pass, c.host)
|
c.smtpAuth = smtp.LoginAuth(c.user, c.pass, c.host)
|
||||||
case SMTPAuthCramMD5:
|
case SMTPAuthCramMD5:
|
||||||
if !strings.Contains(sat, string(SMTPAuthCramMD5)) {
|
if !strings.Contains(sat, string(SMTPAuthCramMD5)) {
|
||||||
return ErrCramMD5AuthNotSupported
|
return ErrCramMD5AuthNotSupported
|
||||||
}
|
}
|
||||||
c.sa = smtp.CRAMMD5Auth(c.user, c.pass)
|
c.smtpAuth = smtp.CRAMMD5Auth(c.user, c.pass)
|
||||||
case SMTPAuthXOAUTH2:
|
case SMTPAuthXOAUTH2:
|
||||||
if !strings.Contains(sat, string(SMTPAuthXOAUTH2)) {
|
if !strings.Contains(sat, string(SMTPAuthXOAUTH2)) {
|
||||||
return ErrXOauth2AuthNotSupported
|
return ErrXOauth2AuthNotSupported
|
||||||
}
|
}
|
||||||
c.sa = smtp.XOAuth2Auth(c.user, c.pass)
|
c.smtpAuth = smtp.XOAuth2Auth(c.user, c.pass)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported SMTP AUTH type %q", c.satype)
|
return fmt.Errorf("unsupported SMTP AUTH type %q", c.smtpAuthType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.sa != nil {
|
if c.smtpAuth != nil {
|
||||||
if err := c.sc.Auth(c.sa); err != nil {
|
if err := c.smtpClient.Auth(c.smtpAuth); err != nil {
|
||||||
return fmt.Errorf("SMTP AUTH failed: %w", err)
|
return fmt.Errorf("SMTP AUTH failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
for _, m := range ml {
|
for _, m := range ml {
|
||||||
m.sendError = nil
|
m.sendError = nil
|
||||||
if m.encoding == NoEncoding {
|
if m.encoding == NoEncoding {
|
||||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
if ok, _ := c.smtpClient.Extension("8BITMIME"); !ok {
|
||||||
se := &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
se := &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||||
m.sendError = se
|
m.sendError = se
|
||||||
errs = append(errs, se)
|
errs = append(errs, se)
|
||||||
|
@ -42,12 +42,12 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
|
|
||||||
if c.dsn {
|
if c.dsn {
|
||||||
if c.dsnmrtype != "" {
|
if c.dsnmrtype != "" {
|
||||||
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := c.sc.Mail(f); err != nil {
|
if err := c.smtpClient.Mail(f); err != nil {
|
||||||
se := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||||
se.errlist = append(se.errlist, reserr)
|
se.errlist = append(se.errlist, reserr)
|
||||||
}
|
}
|
||||||
m.sendError = se
|
m.sendError = se
|
||||||
|
@ -59,9 +59,9 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
rse.errlist = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rse.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
rno := strings.Join(c.dsnrntype, ",")
|
rno := strings.Join(c.dsnrntype, ",")
|
||||||
c.sc.SetDSNRcptNotifyOption(rno)
|
c.smtpClient.SetDSNRcptNotifyOption(rno)
|
||||||
for _, r := range rl {
|
for _, r := range rl {
|
||||||
if err := c.sc.Rcpt(r); err != nil {
|
if err := c.smtpClient.Rcpt(r); err != nil {
|
||||||
rse.Reason = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rse.errlist = append(rse.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rse.rcpt = append(rse.rcpt, r)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
|
@ -70,14 +70,14 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||||
rse.errlist = append(rse.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
}
|
}
|
||||||
m.sendError = rse
|
m.sendError = rse
|
||||||
errs = append(errs, rse)
|
errs = append(errs, rse)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w, err := c.sc.Data()
|
w, err := c.smtpClient.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
se := &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
m.sendError = se
|
m.sendError = se
|
||||||
|
|
|
@ -21,7 +21,7 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
for _, m := range ml {
|
for _, m := range ml {
|
||||||
m.sendError = nil
|
m.sendError = nil
|
||||||
if m.encoding == NoEncoding {
|
if m.encoding == NoEncoding {
|
||||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
if ok, _ := c.smtpClient.Extension("8BITMIME"); !ok {
|
||||||
m.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
m.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||||
rerr = errors.Join(rerr, m.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
|
@ -42,13 +42,13 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
|
|
||||||
if c.dsn {
|
if c.dsn {
|
||||||
if c.dsnmrtype != "" {
|
if c.dsnmrtype != "" {
|
||||||
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := c.sc.Mail(f); err != nil {
|
if err := c.smtpClient.Mail(f); err != nil {
|
||||||
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
rerr = errors.Join(rerr, m.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||||
rerr = errors.Join(rerr, reserr)
|
rerr = errors.Join(rerr, reserr)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
@ -58,9 +58,9 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
rse.errlist = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rse.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
rno := strings.Join(c.dsnrntype, ",")
|
rno := strings.Join(c.dsnrntype, ",")
|
||||||
c.sc.SetDSNRcptNotifyOption(rno)
|
c.smtpClient.SetDSNRcptNotifyOption(rno)
|
||||||
for _, r := range rl {
|
for _, r := range rl {
|
||||||
if err := c.sc.Rcpt(r); err != nil {
|
if err := c.smtpClient.Rcpt(r); err != nil {
|
||||||
rse.Reason = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rse.errlist = append(rse.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rse.rcpt = append(rse.rcpt, r)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
|
@ -69,14 +69,14 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||||
rerr = errors.Join(rerr, reserr)
|
rerr = errors.Join(rerr, reserr)
|
||||||
}
|
}
|
||||||
m.sendError = rse
|
m.sendError = rse
|
||||||
rerr = errors.Join(rerr, m.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w, err := c.sc.Data()
|
w, err := c.smtpClient.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
rerr = errors.Join(rerr, m.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
|
|
|
@ -49,9 +49,9 @@ func TestNewClient(t *testing.T) {
|
||||||
if c.host != tt.host {
|
if c.host != tt.host {
|
||||||
t.Errorf("failed to create new client. Host expected: %s, got: %s", host, c.host)
|
t.Errorf("failed to create new client. Host expected: %s, got: %s", host, c.host)
|
||||||
}
|
}
|
||||||
if c.cto != DefaultTimeout {
|
if c.connTimeout != DefaultTimeout {
|
||||||
t.Errorf("failed to create new client. Timeout expected: %s, got: %s", DefaultTimeout.String(),
|
t.Errorf("failed to create new client. Timeout expected: %s, got: %s", DefaultTimeout.String(),
|
||||||
c.cto.String())
|
c.connTimeout.String())
|
||||||
}
|
}
|
||||||
if c.port != DefaultPort {
|
if c.port != DefaultPort {
|
||||||
t.Errorf("failed to create new client. Port expected: %d, got: %d", DefaultPort, c.port)
|
t.Errorf("failed to create new client. Port expected: %d, got: %d", DefaultPort, c.port)
|
||||||
|
@ -205,8 +205,8 @@ func TestWithTimeout(t *testing.T) {
|
||||||
t.Errorf("failed to create new client: %s", err)
|
t.Errorf("failed to create new client: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.cto != tt.want {
|
if c.connTimeout != tt.want {
|
||||||
t.Errorf("failed to set custom timeout. Want: %d, got: %d", tt.want, c.cto)
|
t.Errorf("failed to set custom timeout. Want: %d, got: %d", tt.want, c.connTimeout)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -345,8 +345,8 @@ func TestSetSSL(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSSL(tt.value)
|
c.SetSSL(tt.value)
|
||||||
if c.ssl != tt.value {
|
if c.useSSL != tt.value {
|
||||||
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.ssl, tt.value)
|
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.useSSL, tt.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -374,8 +374,8 @@ func TestClient_SetSSLPort(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSSLPort(tt.value, tt.fb)
|
c.SetSSLPort(tt.value, tt.fb)
|
||||||
if c.ssl != tt.value {
|
if c.useSSL != tt.value {
|
||||||
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.ssl, tt.value)
|
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.useSSL, tt.value)
|
||||||
}
|
}
|
||||||
if c.port != tt.port {
|
if c.port != tt.port {
|
||||||
t.Errorf("failed to set SSLPort, wanted port: %d, got: %d", c.port, tt.port)
|
t.Errorf("failed to set SSLPort, wanted port: %d, got: %d", c.port, tt.port)
|
||||||
|
@ -460,8 +460,8 @@ func TestSetSMTPAuth(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSMTPAuth(tt.value)
|
c.SetSMTPAuth(tt.value)
|
||||||
if string(c.satype) != tt.want {
|
if string(c.smtpAuthType) != tt.want {
|
||||||
t.Errorf("failed to set SMTP auth type. Expected %s, got: %s", tt.want, string(c.satype))
|
t.Errorf("failed to set SMTP auth type. Expected %s, got: %s", tt.want, string(c.smtpAuthType))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -590,10 +590,10 @@ func TestSetSMTPAuthCustom(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSMTPAuthCustom(tt.value)
|
c.SetSMTPAuthCustom(tt.value)
|
||||||
if c.sa == nil {
|
if c.smtpAuth == nil {
|
||||||
t.Errorf("failed to set custom SMTP auth method. SMTP Auth method is empty")
|
t.Errorf("failed to set custom SMTP auth method. SMTP Auth method is empty")
|
||||||
}
|
}
|
||||||
p, _, err := c.sa.Start(&si)
|
p, _, err := c.smtpAuth.Start(&si)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("SMTP Auth Start() method returned error: %s", err)
|
t.Errorf("SMTP Auth Start() method returned error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -615,10 +615,10 @@ func TestClient_DialWithContext(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.co == nil {
|
if c.connection == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.sc == nil {
|
if c.smtpClient == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
|
@ -640,10 +640,10 @@ func TestClient_DialWithContext_Fallback(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.co == nil {
|
if c.connection == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.sc == nil {
|
if c.smtpClient == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
|
@ -670,10 +670,10 @@ func TestClient_DialWithContext_Debug(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.co == nil {
|
if c.connection == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.sc == nil {
|
if c.smtpClient == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
c.SetDebugLog(true)
|
c.SetDebugLog(true)
|
||||||
|
@ -694,10 +694,10 @@ func TestClient_DialWithContext_Debug_custom(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.co == nil {
|
if c.connection == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.sc == nil {
|
if c.smtpClient == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
c.SetDebugLog(true)
|
c.SetDebugLog(true)
|
||||||
|
@ -714,7 +714,7 @@ func TestClient_DialWithContextInvalidHost(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||||
}
|
}
|
||||||
c.co = nil
|
c.connection = nil
|
||||||
c.host = "invalid.addr"
|
c.host = "invalid.addr"
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := c.DialWithContext(ctx); err == nil {
|
if err := c.DialWithContext(ctx); err == nil {
|
||||||
|
@ -730,7 +730,7 @@ func TestClient_DialWithContextInvalidHELO(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||||
}
|
}
|
||||||
c.co = nil
|
c.connection = nil
|
||||||
c.helo = ""
|
c.helo = ""
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := c.DialWithContext(ctx); err == nil {
|
if err := c.DialWithContext(ctx); err == nil {
|
||||||
|
@ -762,7 +762,7 @@ func TestClient_checkConn(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||||
}
|
}
|
||||||
c.co = nil
|
c.connection = nil
|
||||||
if err := c.checkConn(); err == nil {
|
if err := c.checkConn(); err == nil {
|
||||||
t.Errorf("connCheck() should fail but succeeded")
|
t.Errorf("connCheck() should fail but succeeded")
|
||||||
}
|
}
|
||||||
|
@ -799,10 +799,10 @@ func TestClient_DialWithContextOptions(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !tt.sf {
|
if !tt.sf {
|
||||||
if c.co == nil && !tt.sf {
|
if c.connection == nil && !tt.sf {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.sc == nil && !tt.sf {
|
if c.smtpClient == nil && !tt.sf {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
if err := c.Reset(); err != nil {
|
if err := c.Reset(); err != nil {
|
||||||
|
@ -1002,16 +1002,16 @@ func TestClient_DialSendCloseBroken(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closestart {
|
if tt.closestart {
|
||||||
_ = c.sc.Close()
|
_ = c.smtpClient.Close()
|
||||||
_ = c.co.Close()
|
_ = c.connection.Close()
|
||||||
}
|
}
|
||||||
if err := c.Send(m); err != nil && !tt.sf {
|
if err := c.Send(m); err != nil && !tt.sf {
|
||||||
t.Errorf("Send() failed: %s", err)
|
t.Errorf("Send() failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closeearly {
|
if tt.closeearly {
|
||||||
_ = c.sc.Close()
|
_ = c.smtpClient.Close()
|
||||||
_ = c.co.Close()
|
_ = c.connection.Close()
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil && !tt.sf {
|
if err := c.Close(); err != nil && !tt.sf {
|
||||||
t.Errorf("Close() failed: %s", err)
|
t.Errorf("Close() failed: %s", err)
|
||||||
|
@ -1062,16 +1062,16 @@ func TestClient_DialSendCloseBrokenWithDSN(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closestart {
|
if tt.closestart {
|
||||||
_ = c.sc.Close()
|
_ = c.smtpClient.Close()
|
||||||
_ = c.co.Close()
|
_ = c.connection.Close()
|
||||||
}
|
}
|
||||||
if err := c.Send(m); err != nil && !tt.sf {
|
if err := c.Send(m); err != nil && !tt.sf {
|
||||||
t.Errorf("Send() failed: %s", err)
|
t.Errorf("Send() failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closeearly {
|
if tt.closeearly {
|
||||||
_ = c.sc.Close()
|
_ = c.smtpClient.Close()
|
||||||
_ = c.co.Close()
|
_ = c.connection.Close()
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil && !tt.sf {
|
if err := c.Close(); err != nil && !tt.sf {
|
||||||
t.Errorf("Close() failed: %s", err)
|
t.Errorf("Close() failed: %s", err)
|
||||||
|
|
408
msg.go
408
msg.go
|
@ -708,7 +708,10 @@ func (m *Msg) SetBodyString(contentType ContentType, content string, opts ...Par
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBodyWriter sets the body of the message.
|
// SetBodyWriter sets the body of the message.
|
||||||
func (m *Msg) SetBodyWriter(contentType ContentType, writeFunc func(io.Writer) (int64, error), opts ...PartOption) {
|
func (m *Msg) SetBodyWriter(
|
||||||
|
contentType ContentType, writeFunc func(io.Writer) (int64, error),
|
||||||
|
opts ...PartOption,
|
||||||
|
) {
|
||||||
p := m.newPart(contentType, opts...)
|
p := m.newPart(contentType, opts...)
|
||||||
p.w = writeFunc
|
p.w = writeFunc
|
||||||
m.parts = []*Part{p}
|
m.parts = []*Part{p}
|
||||||
|
@ -716,85 +719,88 @@ func (m *Msg) SetBodyWriter(contentType ContentType, writeFunc func(io.Writer) (
|
||||||
|
|
||||||
// SetBodyHTMLTemplate sets the body of the message from a given html/template.Template pointer
|
// SetBodyHTMLTemplate sets the body of the message from a given html/template.Template pointer
|
||||||
// The content type will be set to text/html automatically
|
// The content type will be set to text/html automatically
|
||||||
func (m *Msg) SetBodyHTMLTemplate(t *ht.Template, d interface{}, o ...PartOption) error {
|
func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||||
if t == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return fmt.Errorf(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buf := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := t.Execute(&buf, d); err != nil {
|
if err := tpl.Execute(&buffer, data); err != nil {
|
||||||
return fmt.Errorf(errTplExecuteFailed, err)
|
return fmt.Errorf(errTplExecuteFailed, err)
|
||||||
}
|
}
|
||||||
w := writeFuncFromBuffer(&buf)
|
writeFunc := writeFuncFromBuffer(&buffer)
|
||||||
m.SetBodyWriter(TypeTextHTML, w, o...)
|
m.SetBodyWriter(TypeTextHTML, writeFunc, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBodyTextTemplate sets the body of the message from a given text/template.Template pointer
|
// SetBodyTextTemplate sets the body of the message from a given text/template.Template pointer
|
||||||
// The content type will be set to text/plain automatically
|
// The content type will be set to text/plain automatically
|
||||||
func (m *Msg) SetBodyTextTemplate(t *tt.Template, d interface{}, o ...PartOption) error {
|
func (m *Msg) SetBodyTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||||
if t == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return fmt.Errorf(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
if err := t.Execute(&buf, d); err != nil {
|
if err := tpl.Execute(&buf, data); err != nil {
|
||||||
return fmt.Errorf(errTplExecuteFailed, err)
|
return fmt.Errorf(errTplExecuteFailed, err)
|
||||||
}
|
}
|
||||||
w := writeFuncFromBuffer(&buf)
|
writeFunc := writeFuncFromBuffer(&buf)
|
||||||
m.SetBodyWriter(TypeTextPlain, w, o...)
|
m.SetBodyWriter(TypeTextPlain, writeFunc, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAlternativeString sets the alternative body of the message.
|
// AddAlternativeString sets the alternative body of the message.
|
||||||
func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) {
|
func (m *Msg) AddAlternativeString(contentType ContentType, content string, opts ...PartOption) {
|
||||||
buf := bytes.NewBufferString(b)
|
buffer := bytes.NewBufferString(content)
|
||||||
w := writeFuncFromBuffer(buf)
|
writeFunc := writeFuncFromBuffer(buffer)
|
||||||
m.AddAlternativeWriter(ct, w, o...)
|
m.AddAlternativeWriter(contentType, writeFunc, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAlternativeWriter sets the body of the message.
|
// AddAlternativeWriter sets the body of the message.
|
||||||
func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) (int64, error), o ...PartOption) {
|
func (m *Msg) AddAlternativeWriter(
|
||||||
p := m.newPart(ct, o...)
|
contentType ContentType, writeFunc func(io.Writer) (int64, error),
|
||||||
p.w = w
|
opts ...PartOption,
|
||||||
m.parts = append(m.parts, p)
|
) {
|
||||||
|
part := m.newPart(contentType, opts...)
|
||||||
|
part.w = writeFunc
|
||||||
|
m.parts = append(m.parts, part)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAlternativeHTMLTemplate sets the alternative body of the message to a html/template.Template output
|
// AddAlternativeHTMLTemplate sets the alternative body of the message to a html/template.Template output
|
||||||
// The content type will be set to text/html automatically
|
// The content type will be set to text/html automatically
|
||||||
func (m *Msg) AddAlternativeHTMLTemplate(t *ht.Template, d interface{}, o ...PartOption) error {
|
func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||||
if t == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return fmt.Errorf(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buf := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := t.Execute(&buf, d); err != nil {
|
if err := tpl.Execute(&buffer, data); err != nil {
|
||||||
return fmt.Errorf(errTplExecuteFailed, err)
|
return fmt.Errorf(errTplExecuteFailed, err)
|
||||||
}
|
}
|
||||||
w := writeFuncFromBuffer(&buf)
|
writeFunc := writeFuncFromBuffer(&buffer)
|
||||||
m.AddAlternativeWriter(TypeTextHTML, w, o...)
|
m.AddAlternativeWriter(TypeTextHTML, writeFunc, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAlternativeTextTemplate sets the alternative body of the message to a text/template.Template output
|
// AddAlternativeTextTemplate sets the alternative body of the message to a text/template.Template output
|
||||||
// The content type will be set to text/plain automatically
|
// The content type will be set to text/plain automatically
|
||||||
func (m *Msg) AddAlternativeTextTemplate(t *tt.Template, d interface{}, o ...PartOption) error {
|
func (m *Msg) AddAlternativeTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||||
if t == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return fmt.Errorf(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buf := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := t.Execute(&buf, d); err != nil {
|
if err := tpl.Execute(&buffer, data); err != nil {
|
||||||
return fmt.Errorf(errTplExecuteFailed, err)
|
return fmt.Errorf(errTplExecuteFailed, err)
|
||||||
}
|
}
|
||||||
w := writeFuncFromBuffer(&buf)
|
writeFunc := writeFuncFromBuffer(&buffer)
|
||||||
m.AddAlternativeWriter(TypeTextPlain, w, o...)
|
m.AddAlternativeWriter(TypeTextPlain, writeFunc, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachFile adds an attachment File to the Msg
|
// AttachFile adds an attachment File to the Msg
|
||||||
func (m *Msg) AttachFile(n string, o ...FileOption) {
|
func (m *Msg) AttachFile(name string, opts ...FileOption) {
|
||||||
f := fileFromFS(n)
|
file := fileFromFS(name)
|
||||||
if f == nil {
|
if file == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachReader adds an attachment File via io.Reader to the Msg
|
// AttachReader adds an attachment File via io.Reader to the Msg
|
||||||
|
@ -803,61 +809,65 @@ func (m *Msg) AttachFile(n string, o ...FileOption) {
|
||||||
// into memory first, so it can seek through it. Using larger amounts of
|
// into memory first, so it can seek through it. Using larger amounts of
|
||||||
// data on the io.Reader should be avoided. For such, it is recommended to
|
// data on the io.Reader should be avoided. For such, it is recommended to
|
||||||
// either use AttachFile or AttachReadSeeker instead
|
// either use AttachFile or AttachReadSeeker instead
|
||||||
func (m *Msg) AttachReader(n string, r io.Reader, o ...FileOption) error {
|
func (m *Msg) AttachReader(name string, reader io.Reader, opts ...FileOption) error {
|
||||||
f, err := fileFromReader(n, r)
|
file, err := fileFromReader(name, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg
|
// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg
|
||||||
func (m *Msg) AttachReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
|
func (m *Msg) AttachReadSeeker(name string, reader io.ReadSeeker, opts ...FileOption) {
|
||||||
f := fileFromReadSeeker(n, r)
|
file := fileFromReadSeeker(name, reader)
|
||||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg
|
// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg
|
||||||
func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
|
func (m *Msg) AttachHTMLTemplate(
|
||||||
f, err := fileFromHTMLTemplate(n, t, d)
|
name string, tpl *ht.Template, data interface{}, opts ...FileOption,
|
||||||
|
) error {
|
||||||
|
file, err := fileFromHTMLTemplate(name, tpl, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to attach template: %w", err)
|
return fmt.Errorf("failed to attach template: %w", err)
|
||||||
}
|
}
|
||||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachTextTemplate adds the output of a text/template.Template pointer as File attachment to the Msg
|
// AttachTextTemplate adds the output of a text/template.Template pointer as File attachment to the Msg
|
||||||
func (m *Msg) AttachTextTemplate(n string, t *tt.Template, d interface{}, o ...FileOption) error {
|
func (m *Msg) AttachTextTemplate(
|
||||||
f, err := fileFromTextTemplate(n, t, d)
|
name string, tpl *tt.Template, data interface{}, opts ...FileOption,
|
||||||
|
) error {
|
||||||
|
file, err := fileFromTextTemplate(name, tpl, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to attach template: %w", err)
|
return fmt.Errorf("failed to attach template: %w", err)
|
||||||
}
|
}
|
||||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachFromEmbedFS adds an attachment File from an embed.FS to the Msg
|
// AttachFromEmbedFS adds an attachment File from an embed.FS to the Msg
|
||||||
func (m *Msg) AttachFromEmbedFS(n string, f *embed.FS, o ...FileOption) error {
|
func (m *Msg) AttachFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) error {
|
||||||
if f == nil {
|
if fs == nil {
|
||||||
return fmt.Errorf("embed.FS must not be nil")
|
return fmt.Errorf("embed.FS must not be nil")
|
||||||
}
|
}
|
||||||
ef, err := fileFromEmbedFS(n, f)
|
file, err := fileFromEmbedFS(name, fs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.attachments = m.appendFile(m.attachments, ef, o...)
|
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedFile adds an embedded File to the Msg
|
// EmbedFile adds an embedded File to the Msg
|
||||||
func (m *Msg) EmbedFile(n string, o ...FileOption) {
|
func (m *Msg) EmbedFile(name string, opts ...FileOption) {
|
||||||
f := fileFromFS(n)
|
file := fileFromFS(name)
|
||||||
if f == nil {
|
if file == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedReader adds an embedded File from an io.Reader to the Msg
|
// EmbedReader adds an embedded File from an io.Reader to the Msg
|
||||||
|
@ -866,51 +876,55 @@ func (m *Msg) EmbedFile(n string, o ...FileOption) {
|
||||||
// into memory first, so it can seek through it. Using larger amounts of
|
// into memory first, so it can seek through it. Using larger amounts of
|
||||||
// data on the io.Reader should be avoided. For such, it is recommended to
|
// data on the io.Reader should be avoided. For such, it is recommended to
|
||||||
// either use EmbedFile or EmbedReadSeeker instead
|
// either use EmbedFile or EmbedReadSeeker instead
|
||||||
func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) error {
|
func (m *Msg) EmbedReader(name string, reader io.Reader, opts ...FileOption) error {
|
||||||
f, err := fileFromReader(n, r)
|
file, err := fileFromReader(name, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg
|
// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg
|
||||||
func (m *Msg) EmbedReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
|
func (m *Msg) EmbedReadSeeker(name string, reader io.ReadSeeker, opts ...FileOption) {
|
||||||
f := fileFromReadSeeker(n, r)
|
file := fileFromReadSeeker(name, reader)
|
||||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg
|
// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg
|
||||||
func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
|
func (m *Msg) EmbedHTMLTemplate(
|
||||||
f, err := fileFromHTMLTemplate(n, t, d)
|
name string, tpl *ht.Template, data interface{}, opts ...FileOption,
|
||||||
|
) error {
|
||||||
|
file, err := fileFromHTMLTemplate(name, tpl, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to embed template: %w", err)
|
return fmt.Errorf("failed to embed template: %w", err)
|
||||||
}
|
}
|
||||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedTextTemplate adds the output of a text/template.Template pointer as embedded File to the Msg
|
// EmbedTextTemplate adds the output of a text/template.Template pointer as embedded File to the Msg
|
||||||
func (m *Msg) EmbedTextTemplate(n string, t *tt.Template, d interface{}, o ...FileOption) error {
|
func (m *Msg) EmbedTextTemplate(
|
||||||
f, err := fileFromTextTemplate(n, t, d)
|
name string, tpl *tt.Template, data interface{}, opts ...FileOption,
|
||||||
|
) error {
|
||||||
|
file, err := fileFromTextTemplate(name, tpl, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to embed template: %w", err)
|
return fmt.Errorf("failed to embed template: %w", err)
|
||||||
}
|
}
|
||||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedFromEmbedFS adds an embedded File from an embed.FS to the Msg
|
// EmbedFromEmbedFS adds an embedded File from an embed.FS to the Msg
|
||||||
func (m *Msg) EmbedFromEmbedFS(n string, f *embed.FS, o ...FileOption) error {
|
func (m *Msg) EmbedFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) error {
|
||||||
if f == nil {
|
if fs == nil {
|
||||||
return fmt.Errorf("embed.FS must not be nil")
|
return fmt.Errorf("embed.FS must not be nil")
|
||||||
}
|
}
|
||||||
ef, err := fileFromEmbedFS(n, f)
|
file, err := fileFromEmbedFS(name, fs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.embeds = m.appendFile(m.embeds, ef, o...)
|
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -925,73 +939,73 @@ func (m *Msg) Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyMiddlewares apply the list of middlewares to a Msg
|
// ApplyMiddlewares apply the list of middlewares to a Msg
|
||||||
func (m *Msg) applyMiddlewares(ms *Msg) *Msg {
|
func (m *Msg) applyMiddlewares(msg *Msg) *Msg {
|
||||||
for _, mw := range m.middlewares {
|
for _, middleware := range m.middlewares {
|
||||||
ms = mw.Handle(ms)
|
msg = middleware.Handle(msg)
|
||||||
}
|
}
|
||||||
return ms
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
|
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
|
||||||
func (m *Msg) WriteTo(w io.Writer) (int64, error) {
|
func (m *Msg) WriteTo(writer io.Writer) (int64, error) {
|
||||||
mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
|
mw := &msgWriter{writer: writer, charset: m.charset, encoder: m.encoder}
|
||||||
mw.writeMsg(m.applyMiddlewares(m))
|
mw.writeMsg(m.applyMiddlewares(m))
|
||||||
return mw.bytesWritten, mw.err
|
return mw.bytesWritten, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies
|
// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies
|
||||||
// the io.WriteTo interface but will skip the given Middleware
|
// the io.WriteTo interface but will skip the given Middleware
|
||||||
func (m *Msg) WriteToSkipMiddleware(w io.Writer, mt MiddlewareType) (int64, error) {
|
func (m *Msg) WriteToSkipMiddleware(writer io.Writer, middleWareType MiddlewareType) (int64, error) {
|
||||||
var omwl, mwl []Middleware
|
var origMiddlewares, middlewares []Middleware
|
||||||
omwl = m.middlewares
|
origMiddlewares = m.middlewares
|
||||||
for i := range m.middlewares {
|
for i := range m.middlewares {
|
||||||
if m.middlewares[i].Type() == mt {
|
if m.middlewares[i].Type() == middleWareType {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mwl = append(mwl, m.middlewares[i])
|
middlewares = append(middlewares, m.middlewares[i])
|
||||||
}
|
}
|
||||||
m.middlewares = mwl
|
m.middlewares = middlewares
|
||||||
mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
|
mw := &msgWriter{writer: writer, charset: m.charset, encoder: m.encoder}
|
||||||
mw.writeMsg(m.applyMiddlewares(m))
|
mw.writeMsg(m.applyMiddlewares(m))
|
||||||
m.middlewares = omwl
|
m.middlewares = origMiddlewares
|
||||||
return mw.bytesWritten, mw.err
|
return mw.bytesWritten, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write is an alias method to WriteTo due to compatibility reasons
|
// Write is an alias method to WriteTo due to compatibility reasons
|
||||||
func (m *Msg) Write(w io.Writer) (int64, error) {
|
func (m *Msg) Write(writer io.Writer) (int64, error) {
|
||||||
return m.WriteTo(w)
|
return m.WriteTo(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendFile adds a File to the Msg (as attachment or embed)
|
// appendFile adds a File to the Msg (as attachment or embed)
|
||||||
func (m *Msg) appendFile(c []*File, f *File, o ...FileOption) []*File {
|
func (m *Msg) appendFile(files []*File, file *File, opts ...FileOption) []*File {
|
||||||
// Override defaults with optionally provided FileOption functions
|
// Override defaults with optionally provided FileOption functions
|
||||||
for _, co := range o {
|
for _, opt := range opts {
|
||||||
if co == nil {
|
if opt == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
co(f)
|
opt(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == nil {
|
if files == nil {
|
||||||
return []*File{f}
|
return []*File{file}
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(c, f)
|
return append(files, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToFile stores the Msg as file on disk. It will try to create the given filename
|
// WriteToFile stores the Msg as file on disk. It will try to create the given filename
|
||||||
// Already existing files will be overwritten
|
// Already existing files will be overwritten
|
||||||
func (m *Msg) WriteToFile(n string) error {
|
func (m *Msg) WriteToFile(name string) error {
|
||||||
f, err := os.Create(n)
|
file, err := os.Create(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create output file: %w", err)
|
return fmt.Errorf("failed to create output file: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { _ = f.Close() }()
|
defer func() { _ = file.Close() }()
|
||||||
_, err = m.WriteTo(f)
|
_, err = m.WriteTo(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write to output file: %w", err)
|
return fmt.Errorf("failed to write to output file: %w", err)
|
||||||
}
|
}
|
||||||
return f.Close()
|
return file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToSendmail returns WriteToSendmailWithCommand with a default sendmail path
|
// WriteToSendmail returns WriteToSendmailWithCommand with a default sendmail path
|
||||||
|
@ -1001,38 +1015,38 @@ func (m *Msg) WriteToSendmail() error {
|
||||||
|
|
||||||
// WriteToSendmailWithCommand returns WriteToSendmailWithContext with a default timeout
|
// WriteToSendmailWithCommand returns WriteToSendmailWithContext with a default timeout
|
||||||
// of 5 seconds and a given sendmail path
|
// of 5 seconds and a given sendmail path
|
||||||
func (m *Msg) WriteToSendmailWithCommand(sp string) error {
|
func (m *Msg) WriteToSendmailWithCommand(sendmailPath string) error {
|
||||||
tctx, tcfn := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
defer tcfn()
|
defer cancel()
|
||||||
return m.WriteToSendmailWithContext(tctx, sp)
|
return m.WriteToSendmailWithContext(ctx, sendmailPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToSendmailWithContext opens an pipe to the local sendmail binary and tries to send the
|
// WriteToSendmailWithContext opens an pipe to the local sendmail binary and tries to send the
|
||||||
// mail though that. It takes a context.Context, the path to the sendmail binary and additional
|
// mail though that. It takes a context.Context, the path to the sendmail binary and additional
|
||||||
// arguments for the sendmail binary as parameters
|
// arguments for the sendmail binary as parameters
|
||||||
func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...string) error {
|
func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sendmailPath string, args ...string) error {
|
||||||
ec := exec.CommandContext(ctx, sp)
|
cmdCtx := exec.CommandContext(ctx, sendmailPath)
|
||||||
ec.Args = append(ec.Args, "-oi", "-t")
|
cmdCtx.Args = append(cmdCtx.Args, "-oi", "-t")
|
||||||
ec.Args = append(ec.Args, a...)
|
cmdCtx.Args = append(cmdCtx.Args, args...)
|
||||||
|
|
||||||
se, err := ec.StderrPipe()
|
stdErr, err := cmdCtx.StderrPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to set STDERR pipe: %w", err)
|
return fmt.Errorf("failed to set STDERR pipe: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
si, err := ec.StdinPipe()
|
stdIn, err := cmdCtx.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to set STDIN pipe: %w", err)
|
return fmt.Errorf("failed to set STDIN pipe: %w", err)
|
||||||
}
|
}
|
||||||
if se == nil || si == nil {
|
if stdErr == nil || stdIn == nil {
|
||||||
return fmt.Errorf("received nil for STDERR or STDIN pipe")
|
return fmt.Errorf("received nil for STDERR or STDIN pipe")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the execution and write to STDIN
|
// Start the execution and write to STDIN
|
||||||
if err = ec.Start(); err != nil {
|
if err = cmdCtx.Start(); err != nil {
|
||||||
return fmt.Errorf("could not start sendmail execution: %w", err)
|
return fmt.Errorf("could not start sendmail execution: %w", err)
|
||||||
}
|
}
|
||||||
_, err = m.WriteTo(si)
|
_, err = m.WriteTo(stdIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, syscall.EPIPE) {
|
if !errors.Is(err, syscall.EPIPE) {
|
||||||
return fmt.Errorf("failed to write mail to buffer: %w", err)
|
return fmt.Errorf("failed to write mail to buffer: %w", err)
|
||||||
|
@ -1040,20 +1054,20 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close STDIN and wait for completion or cancellation of the sendmail executable
|
// Close STDIN and wait for completion or cancellation of the sendmail executable
|
||||||
if err = si.Close(); err != nil {
|
if err = stdIn.Close(); err != nil {
|
||||||
return fmt.Errorf("failed to close STDIN pipe: %w", err)
|
return fmt.Errorf("failed to close STDIN pipe: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the stderr pipe for possible errors
|
// Read the stderr pipe for possible errors
|
||||||
serr, err := io.ReadAll(se)
|
sendmailErr, err := io.ReadAll(stdErr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read STDERR pipe: %w", err)
|
return fmt.Errorf("failed to read STDERR pipe: %w", err)
|
||||||
}
|
}
|
||||||
if len(serr) > 0 {
|
if len(sendmailErr) > 0 {
|
||||||
return fmt.Errorf("sendmail command failed: %s", string(serr))
|
return fmt.Errorf("sendmail command failed: %s", string(sendmailErr))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ec.Wait(); err != nil {
|
if err = cmdCtx.Wait(); err != nil {
|
||||||
return fmt.Errorf("sendmail command execution failed: %w", err)
|
return fmt.Errorf("sendmail command execution failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,24 +1081,24 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...st
|
||||||
// changes will not be reflected in the Reader. You will have to use Msg.UpdateReader
|
// changes will not be reflected in the Reader. You will have to use Msg.UpdateReader
|
||||||
// first to update the Reader's buffer with the current Msg content
|
// first to update the Reader's buffer with the current Msg content
|
||||||
func (m *Msg) NewReader() *Reader {
|
func (m *Msg) NewReader() *Reader {
|
||||||
r := &Reader{}
|
reader := &Reader{}
|
||||||
wbuf := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
_, err := m.Write(&wbuf)
|
_, err := m.Write(&buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.err = fmt.Errorf("failed to write Msg to Reader buffer: %w", err)
|
reader.err = fmt.Errorf("failed to write Msg to Reader buffer: %w", err)
|
||||||
}
|
}
|
||||||
r.buf = wbuf.Bytes()
|
reader.buf = buffer.Bytes()
|
||||||
return r
|
return reader
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateReader will update a Reader with the content of the given Msg and reset the
|
// UpdateReader will update a Reader with the content of the given Msg and reset the
|
||||||
// Reader position to the start
|
// Reader position to the start
|
||||||
func (m *Msg) UpdateReader(r *Reader) {
|
func (m *Msg) UpdateReader(reader *Reader) {
|
||||||
wbuf := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
_, err := m.Write(&wbuf)
|
_, err := m.Write(&buffer)
|
||||||
r.Reset()
|
reader.Reset()
|
||||||
r.buf = wbuf.Bytes()
|
reader.buf = buffer.Bytes()
|
||||||
r.err = err
|
reader.err = err
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasSendError returns true if the Msg experienced an error during the message delivery and the
|
// HasSendError returns true if the Msg experienced an error during the message delivery and the
|
||||||
|
@ -1096,9 +1110,9 @@ func (m *Msg) HasSendError() bool {
|
||||||
// SendErrorIsTemp returns true if the Msg experienced an error during the message delivery and the
|
// SendErrorIsTemp returns true if the Msg experienced an error during the message delivery and the
|
||||||
// corresponding error was of temporary nature and should be retried later
|
// corresponding error was of temporary nature and should be retried later
|
||||||
func (m *Msg) SendErrorIsTemp() bool {
|
func (m *Msg) SendErrorIsTemp() bool {
|
||||||
var e *SendError
|
var err *SendError
|
||||||
if errors.As(m.sendError, &e) && e != nil {
|
if errors.As(m.sendError, &err) && err != nil {
|
||||||
return e.isTemp
|
return err.isTemp
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1110,19 +1124,19 @@ func (m *Msg) SendError() error {
|
||||||
|
|
||||||
// encodeString encodes a string based on the configured message encoder and the corresponding
|
// encodeString encodes a string based on the configured message encoder and the corresponding
|
||||||
// charset for the Msg
|
// charset for the Msg
|
||||||
func (m *Msg) encodeString(s string) string {
|
func (m *Msg) encodeString(str string) string {
|
||||||
return m.encoder.Encode(string(m.charset), s)
|
return m.encoder.Encode(string(m.charset), str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasAlt returns true if the Msg has more than one part
|
// hasAlt returns true if the Msg has more than one part
|
||||||
func (m *Msg) hasAlt() bool {
|
func (m *Msg) hasAlt() bool {
|
||||||
c := 0
|
count := 0
|
||||||
for _, p := range m.parts {
|
for _, part := range m.parts {
|
||||||
if !p.del {
|
if !part.del {
|
||||||
c++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c > 1 && m.pgptype == 0
|
return count > 1 && m.pgptype == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasMixed returns true if the Msg has mixed parts
|
// hasMixed returns true if the Msg has mixed parts
|
||||||
|
@ -1141,19 +1155,19 @@ func (m *Msg) hasPGPType() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPart returns a new Part for the Msg
|
// newPart returns a new Part for the Msg
|
||||||
func (m *Msg) newPart(ct ContentType, o ...PartOption) *Part {
|
func (m *Msg) newPart(contentType ContentType, opts ...PartOption) *Part {
|
||||||
p := &Part{
|
p := &Part{
|
||||||
ctype: ct,
|
ctype: contentType,
|
||||||
cset: m.charset,
|
cset: m.charset,
|
||||||
enc: m.encoding,
|
enc: m.encoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override defaults with optionally provided MsgOption functions
|
// Override defaults with optionally provided MsgOption functions
|
||||||
for _, co := range o {
|
for _, opt := range opts {
|
||||||
if co == nil {
|
if opt == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
co(p)
|
opt(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
@ -1187,118 +1201,118 @@ func (m *Msg) addDefaultHeader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileFromEmbedFS returns a File pointer from a given file in the provided embed.FS
|
// fileFromEmbedFS returns a File pointer from a given file in the provided embed.FS
|
||||||
func fileFromEmbedFS(n string, f *embed.FS) (*File, error) {
|
func fileFromEmbedFS(name string, fs *embed.FS) (*File, error) {
|
||||||
_, err := f.Open(n)
|
_, err := fs.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open file from embed.FS: %w", err)
|
return nil, fmt.Errorf("failed to open file from embed.FS: %w", err)
|
||||||
}
|
}
|
||||||
return &File{
|
return &File{
|
||||||
Name: filepath.Base(n),
|
Name: filepath.Base(name),
|
||||||
Header: make(map[string][]string),
|
Header: make(map[string][]string),
|
||||||
Writer: func(w io.Writer) (int64, error) {
|
Writer: func(writer io.Writer) (int64, error) {
|
||||||
h, err := f.Open(n)
|
file, err := fs.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
nb, err := io.Copy(w, h)
|
numBytes, err := io.Copy(writer, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = h.Close()
|
_ = file.Close()
|
||||||
return nb, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
return numBytes, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
||||||
}
|
}
|
||||||
return nb, h.Close()
|
return numBytes, file.Close()
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileFromFS returns a File pointer from a given file in the system's file system
|
// fileFromFS returns a File pointer from a given file in the system's file system
|
||||||
func fileFromFS(n string) *File {
|
func fileFromFS(name string) *File {
|
||||||
_, err := os.Stat(n)
|
_, err := os.Stat(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &File{
|
return &File{
|
||||||
Name: filepath.Base(n),
|
Name: filepath.Base(name),
|
||||||
Header: make(map[string][]string),
|
Header: make(map[string][]string),
|
||||||
Writer: func(w io.Writer) (int64, error) {
|
Writer: func(writer io.Writer) (int64, error) {
|
||||||
h, err := os.Open(n)
|
file, err := os.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
nb, err := io.Copy(w, h)
|
numBytes, err := io.Copy(writer, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = h.Close()
|
_ = file.Close()
|
||||||
return nb, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
return numBytes, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
||||||
}
|
}
|
||||||
return nb, h.Close()
|
return numBytes, file.Close()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileFromReader returns a File pointer from a given io.Reader
|
// fileFromReader returns a File pointer from a given io.Reader
|
||||||
func fileFromReader(n string, r io.Reader) (*File, error) {
|
func fileFromReader(name string, reader io.Reader) (*File, error) {
|
||||||
d, err := io.ReadAll(r)
|
d, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &File{}, err
|
return &File{}, err
|
||||||
}
|
}
|
||||||
br := bytes.NewReader(d)
|
byteReader := bytes.NewReader(d)
|
||||||
return &File{
|
return &File{
|
||||||
Name: n,
|
Name: name,
|
||||||
Header: make(map[string][]string),
|
Header: make(map[string][]string),
|
||||||
Writer: func(w io.Writer) (int64, error) {
|
Writer: func(writer io.Writer) (int64, error) {
|
||||||
rb, cerr := io.Copy(w, br)
|
readBytes, copyErr := io.Copy(writer, byteReader)
|
||||||
if cerr != nil {
|
if copyErr != nil {
|
||||||
return rb, cerr
|
return readBytes, copyErr
|
||||||
}
|
}
|
||||||
_, cerr = br.Seek(0, io.SeekStart)
|
_, copyErr = byteReader.Seek(0, io.SeekStart)
|
||||||
return rb, cerr
|
return readBytes, copyErr
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker
|
// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker
|
||||||
func fileFromReadSeeker(n string, r io.ReadSeeker) *File {
|
func fileFromReadSeeker(name string, reader io.ReadSeeker) *File {
|
||||||
return &File{
|
return &File{
|
||||||
Name: n,
|
Name: name,
|
||||||
Header: make(map[string][]string),
|
Header: make(map[string][]string),
|
||||||
Writer: func(w io.Writer) (int64, error) {
|
Writer: func(writer io.Writer) (int64, error) {
|
||||||
rb, err := io.Copy(w, r)
|
readBytes, err := io.Copy(writer, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rb, err
|
return readBytes, err
|
||||||
}
|
}
|
||||||
_, err = r.Seek(0, io.SeekStart)
|
_, err = reader.Seek(0, io.SeekStart)
|
||||||
return rb, err
|
return readBytes, err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileFromHTMLTemplate returns a File pointer form a given html/template.Template
|
// fileFromHTMLTemplate returns a File pointer form a given html/template.Template
|
||||||
func fileFromHTMLTemplate(n string, t *ht.Template, d interface{}) (*File, error) {
|
func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*File, error) {
|
||||||
if t == nil {
|
if tpl == nil {
|
||||||
return nil, fmt.Errorf(errTplPointerNil)
|
return nil, fmt.Errorf(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buf := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := t.Execute(&buf, d); err != nil {
|
if err := tpl.Execute(&buffer, data); err != nil {
|
||||||
return nil, fmt.Errorf(errTplExecuteFailed, err)
|
return nil, fmt.Errorf(errTplExecuteFailed, err)
|
||||||
}
|
}
|
||||||
return fileFromReader(n, &buf)
|
return fileFromReader(name, &buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileFromTextTemplate returns a File pointer form a given text/template.Template
|
// fileFromTextTemplate returns a File pointer form a given text/template.Template
|
||||||
func fileFromTextTemplate(n string, t *tt.Template, d interface{}) (*File, error) {
|
func fileFromTextTemplate(name string, tpl *tt.Template, data interface{}) (*File, error) {
|
||||||
if t == nil {
|
if tpl == nil {
|
||||||
return nil, fmt.Errorf(errTplPointerNil)
|
return nil, fmt.Errorf(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buf := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := t.Execute(&buf, d); err != nil {
|
if err := tpl.Execute(&buffer, data); err != nil {
|
||||||
return nil, fmt.Errorf(errTplExecuteFailed, err)
|
return nil, fmt.Errorf(errTplExecuteFailed, err)
|
||||||
}
|
}
|
||||||
return fileFromReader(n, &buf)
|
return fileFromReader(name, &buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
func getEncoder(e Encoding) mime.WordEncoder {
|
func getEncoder(enc Encoding) mime.WordEncoder {
|
||||||
switch e {
|
switch enc {
|
||||||
case EncodingQP:
|
case EncodingQP:
|
||||||
return mime.QEncoding
|
return mime.QEncoding
|
||||||
case EncodingB64:
|
case EncodingB64:
|
||||||
|
@ -1310,10 +1324,10 @@ func getEncoder(e Encoding) mime.WordEncoder {
|
||||||
|
|
||||||
// writeFuncFromBuffer is a common method to convert a byte buffer into a writeFunc as
|
// writeFuncFromBuffer is a common method to convert a byte buffer into a writeFunc as
|
||||||
// often required by this library
|
// often required by this library
|
||||||
func writeFuncFromBuffer(buf *bytes.Buffer) func(io.Writer) (int64, error) {
|
func writeFuncFromBuffer(buffer *bytes.Buffer) func(io.Writer) (int64, error) {
|
||||||
w := func(w io.Writer) (int64, error) {
|
writeFunc := func(w io.Writer) (int64, error) {
|
||||||
nb, err := w.Write(buf.Bytes())
|
numBytes, err := w.Write(buffer.Bytes())
|
||||||
return int64(nb), err
|
return int64(numBytes), err
|
||||||
}
|
}
|
||||||
return w
|
return writeFunc
|
||||||
}
|
}
|
||||||
|
|
8
tls.go
8
tls.go
|
@ -8,17 +8,17 @@ package mail
|
||||||
type TLSPolicy int
|
type TLSPolicy int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TLSMandatory requires that the connection cto the server is
|
// TLSMandatory requires that the connection to the server is
|
||||||
// encrypting using STARTTLS. If the server does not support STARTTLS
|
// encrypting using STARTTLS. If the server does not support STARTTLS
|
||||||
// the connection will be terminated with an error
|
// the connection will be terminated with an error
|
||||||
TLSMandatory TLSPolicy = iota
|
TLSMandatory TLSPolicy = iota
|
||||||
|
|
||||||
// TLSOpportunistic tries cto establish an encrypted connection via the
|
// TLSOpportunistic tries to establish an encrypted connection via the
|
||||||
// STARTTLS protocol. If the server does not support this, it will fall
|
// STARTTLS protocol. If the server does not support this, it will fall
|
||||||
// back cto non-encrypted plaintext transmission
|
// back to non-encrypted plaintext transmission
|
||||||
TLSOpportunistic
|
TLSOpportunistic
|
||||||
|
|
||||||
// NoTLS forces the transaction cto be not encrypted
|
// NoTLS forces the transaction to be not encrypted
|
||||||
NoTLS
|
NoTLS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue