2023-01-15 16:14:19 +01:00
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
2022-06-17 15:05:54 +02:00
//
// SPDX-License-Identifier: MIT
2022-03-09 16:52:23 +01:00
package mail
import (
2022-03-13 17:15:23 +01:00
"bytes"
2022-03-14 15:26:53 +01:00
"context"
2022-07-07 10:46:57 +02:00
"embed"
2022-03-12 15:10:01 +01:00
"errors"
2022-03-09 16:52:23 +01:00
"fmt"
2022-06-03 10:40:54 +02:00
ht "html/template"
2022-03-12 15:10:01 +01:00
"io"
2022-03-11 10:29:44 +01:00
"mime"
2022-03-10 16:19:51 +01:00
"net/mail"
2022-03-09 16:52:23 +01:00
"os"
2022-03-14 15:26:53 +01:00
"os/exec"
2022-03-14 10:29:53 +01:00
"path/filepath"
2023-11-10 18:07:56 +01:00
"strings"
2022-03-18 23:28:11 +01:00
"syscall"
2022-06-03 10:40:54 +02:00
tt "text/template"
2022-03-09 16:52:23 +01:00
"time"
)
2022-03-12 15:10:01 +01:00
var (
// ErrNoFromAddress should be used when a FROM address is requrested but not set
ErrNoFromAddress = errors . New ( "no FROM address set" )
// ErrNoRcptAddresses should be used when the list of RCPTs is empty
ErrNoRcptAddresses = errors . New ( "no recipient addresses set" )
)
2022-06-03 12:27:26 +02:00
const (
// errTplExecuteFailed is issued when the template execution was not successful
errTplExecuteFailed = "failed to execute template: %w"
// errTplPointerNil is issued when a template pointer is expected but it is nil
errTplPointerNil = "template pointer is nil"
2022-09-09 11:35:45 +02:00
// errParseMailAddr is used when a mail address could not be validated
errParseMailAddr = "failed to parse mail address %q: %w"
2022-06-03 12:27:26 +02:00
)
2023-01-31 18:35:48 +01:00
const (
// NoPGP indicates that a message should not be treated as PGP encrypted
// or signed and is the default value for a message
NoPGP PGPType = iota
// PGPEncrypt indicates that a message should be treated as PGP encrypted
// This works closely together with the corresponding go-mail-middleware
PGPEncrypt
// PGPSignature indicates that a message should be treated as PGP signed
// This works closely together with the corresponding go-mail-middleware
PGPSignature
)
2022-10-25 16:42:18 +02:00
// MiddlewareType is the type description of the Middleware and needs to be returned
// in the Middleware interface by the Type method
type MiddlewareType string
2022-09-22 18:05:47 +02:00
// Middleware is an interface to define a function to apply to Msg before sending
type Middleware interface {
Handle ( * Msg ) * Msg
2022-10-25 16:42:18 +02:00
Type ( ) MiddlewareType
2022-09-22 18:05:47 +02:00
}
2023-01-31 18:35:48 +01:00
// PGPType is a type alias for a int representing a type of PGP encryption
// or signature
type PGPType int
2022-03-09 16:52:23 +01:00
// Msg is the mail message struct
type Msg struct {
2022-03-13 17:15:23 +01:00
// addrHeader is a slice of strings that the different mail AddrHeader fields
addrHeader map [ AddrHeader ] [ ] * mail . Address
2022-03-18 22:23:03 +01:00
// attachments represent the different attachment File of the Msg
attachments [ ] * File
2022-03-13 17:15:23 +01:00
// boundary is the MIME content boundary
boundary string
2022-03-09 16:52:23 +01:00
// charset represents the charset of the mail (defaults to UTF-8)
2022-03-11 10:29:44 +01:00
charset Charset
2022-03-18 22:23:03 +01:00
// embeds represent the different embedded File of the Msg
embeds [ ] * File
2022-03-09 16:52:23 +01:00
// encoder represents a mime.WordEncoder from the std lib
2022-03-11 10:29:44 +01:00
encoder mime . WordEncoder
2022-03-09 16:52:23 +01:00
2022-03-18 22:23:03 +01:00
// encoding represents the message encoding (the encoder will be a corresponding WordEncoder)
encoding Encoding
2022-03-10 16:19:51 +01:00
// genHeader is a slice of strings that the different generic mail Header fields
genHeader map [ Header ] [ ] string
2024-01-23 11:01:08 +01:00
// isDelivered signals if a message has been delivered or not
isDelivered bool
2023-01-31 18:35:48 +01:00
// middlewares is the list of middlewares to apply to the Msg before sending in FIFO order
middlewares [ ] Middleware
2022-10-26 15:33:03 +02:00
2022-03-13 17:15:23 +01:00
// mimever represents the MIME version
mimever MIMEVersion
// parts represent the different parts of the Msg
parts [ ] * Part
2022-09-22 18:05:47 +02:00
2023-01-31 18:35:48 +01:00
// preformHeader is a slice of strings that the different generic mail Header fields
// of which content is already preformated and will not be affected by the automatic line
// breaks
preformHeader map [ Header ] string
// pgptype indicates that a message has a PGPType assigned and therefore will generate
// different Content-Type settings in the msgWriter
pgptype PGPType
2022-12-31 12:40:42 +01:00
// sendError holds the SendError in case a Msg could not be delivered during the Client.Send operation
sendError error
2022-03-14 10:29:53 +01:00
}
2022-03-13 17:15:23 +01:00
2022-03-14 15:26:53 +01:00
// SendmailPath is the default system path to the sendmail binary
2022-05-25 10:53:37 +02:00
const SendmailPath = "/usr/sbin/sendmail"
2022-03-14 15:26:53 +01:00
2022-03-11 10:29:44 +01:00
// MsgOption returns a function that can be used for grouping Msg options
type MsgOption func ( * Msg )
2022-03-09 16:52:23 +01:00
// NewMsg returns a new Msg pointer
2022-03-11 10:29:44 +01:00
func NewMsg ( o ... MsgOption ) * Msg {
2022-03-09 16:52:23 +01:00
m := & Msg {
2022-10-26 15:33:03 +02:00
addrHeader : make ( map [ AddrHeader ] [ ] * mail . Address ) ,
charset : CharsetUTF8 ,
encoding : EncodingQP ,
genHeader : make ( map [ Header ] [ ] string ) ,
preformHeader : make ( map [ Header ] string ) ,
mimever : Mime10 ,
2022-03-09 16:52:23 +01:00
}
2022-03-11 10:29:44 +01:00
// Override defaults with optionally provided MsgOption functions
for _ , co := range o {
if co == nil {
continue
}
co ( m )
}
// Set the matcing mime.WordEncoder for the Msg
m . setEncoder ( )
2022-03-09 16:52:23 +01:00
return m
}
2022-03-11 10:29:44 +01:00
// WithCharset overrides the default message charset
func WithCharset ( c Charset ) MsgOption {
return func ( m * Msg ) {
m . charset = c
}
}
// WithEncoding overrides the default message encoding
func WithEncoding ( e Encoding ) MsgOption {
return func ( m * Msg ) {
m . encoding = e
}
}
2022-03-13 17:15:23 +01:00
// WithMIMEVersion overrides the default MIME version
func WithMIMEVersion ( mv MIMEVersion ) MsgOption {
return func ( m * Msg ) {
m . mimever = mv
}
}
2022-03-18 11:47:50 +01:00
// WithBoundary overrides the default MIME boundary
func WithBoundary ( b string ) MsgOption {
return func ( m * Msg ) {
m . boundary = b
}
}
2022-09-22 18:05:47 +02:00
// WithMiddleware add the given middleware in the end of the list of the client middlewares
func WithMiddleware ( mw Middleware ) MsgOption {
return func ( m * Msg ) {
m . middlewares = append ( m . middlewares , mw )
}
}
2023-01-31 18:35:48 +01:00
// WithPGPType overrides the default PGPType of the message
func WithPGPType ( t PGPType ) MsgOption {
return func ( m * Msg ) {
m . pgptype = t
}
}
2022-03-11 10:29:44 +01:00
// SetCharset sets the encoding charset of the Msg
func ( m * Msg ) SetCharset ( c Charset ) {
m . charset = c
}
// SetEncoding sets the encoding of the Msg
func ( m * Msg ) SetEncoding ( e Encoding ) {
m . encoding = e
2022-03-13 20:01:02 +01:00
m . setEncoder ( )
2022-03-11 10:29:44 +01:00
}
2022-03-13 17:15:23 +01:00
// SetBoundary sets the boundary of the Msg
func ( m * Msg ) SetBoundary ( b string ) {
m . boundary = b
}
// SetMIMEVersion sets the MIME version of the Msg
func ( m * Msg ) SetMIMEVersion ( mv MIMEVersion ) {
m . mimever = mv
}
2023-01-31 18:35:48 +01:00
// SetPGPType sets the PGPType of the Msg
func ( m * Msg ) SetPGPType ( t PGPType ) {
m . pgptype = t
}
2022-03-13 10:49:07 +01:00
// Encoding returns the currently set encoding of the Msg
func ( m * Msg ) Encoding ( ) string {
return m . encoding . String ( )
}
// Charset returns the currently set charset of the Msg
func ( m * Msg ) Charset ( ) string {
return m . charset . String ( )
}
2022-03-10 16:19:51 +01:00
// SetHeader sets a generic header field of the Msg
2022-11-19 11:22:20 +01:00
// For adding address headers like "To:" or "From", see SetAddrHeader
//
// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead
2022-03-09 16:52:23 +01:00
func ( m * Msg ) SetHeader ( h Header , v ... string ) {
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( h , v ... )
}
// SetGenHeader sets a generic header field of the Msg
// For adding address headers like "To:" or "From", see SetAddrHeader
func ( m * Msg ) SetGenHeader ( h Header , v ... string ) {
2022-10-20 18:06:26 +02:00
if m . genHeader == nil {
m . genHeader = make ( map [ Header ] [ ] string )
}
2022-03-11 10:29:44 +01:00
for i , hv := range v {
v [ i ] = m . encodeString ( hv )
}
2022-03-10 16:19:51 +01:00
m . genHeader [ h ] = v
}
2022-10-26 15:33:03 +02:00
// SetHeaderPreformatted sets a generic header field of the Msg which content is
// already preformated.
//
2022-11-19 11:22:20 +01:00
// Deprecated: This method only exists for compatibility reason. Please use
// SetGenHeaderPreformatted instead
func ( m * Msg ) SetHeaderPreformatted ( h Header , v string ) {
m . SetGenHeaderPreformatted ( h , v )
}
// SetGenHeaderPreformatted sets a generic header field of the Msg which content is
// already preformated.
//
2022-10-26 15:33:03 +02:00
// This method does not take a slice of values but only a single value. This is
// due to the fact, that we do not perform any content alteration and expect the
// user has already done so
//
// **Please note:** This method should be used only as a last resort. Since the
// user is respondible for the formating of the message header, go-mail cannot
// guarantee the fully compliance with the RFC 2822. It is recommended to use
2022-11-19 11:22:20 +01:00
// SetGenHeader instead.
func ( m * Msg ) SetGenHeaderPreformatted ( h Header , v string ) {
2022-10-26 15:33:03 +02:00
if m . preformHeader == nil {
m . preformHeader = make ( map [ Header ] string )
}
m . preformHeader [ h ] = v
}
2022-03-10 16:19:51 +01:00
// SetAddrHeader sets an address related header field of the Msg
func ( m * Msg ) SetAddrHeader ( h AddrHeader , v ... string ) error {
2022-10-20 18:06:26 +02:00
if m . addrHeader == nil {
m . addrHeader = make ( map [ AddrHeader ] [ ] * mail . Address )
}
2022-03-10 16:19:51 +01:00
var al [ ] * mail . Address
for _ , av := range v {
2022-03-14 16:25:24 +01:00
a , err := mail . ParseAddress ( av )
2022-03-10 16:19:51 +01:00
if err != nil {
2022-09-09 11:35:45 +02:00
return fmt . Errorf ( errParseMailAddr , av , err )
2022-03-10 16:19:51 +01:00
}
al = append ( al , a )
}
2022-03-09 16:52:23 +01:00
switch h {
case HeaderFrom :
2023-11-20 18:36:19 +01:00
if len ( al ) > 0 {
m . addrHeader [ h ] = [ ] * mail . Address { al [ 0 ] }
}
2022-03-09 16:52:23 +01:00
default :
2022-03-10 16:19:51 +01:00
m . addrHeader [ h ] = al
2022-03-09 16:52:23 +01:00
}
2022-03-10 16:19:51 +01:00
return nil
}
// SetAddrHeaderIgnoreInvalid sets an address related header field of the Msg and ignores invalid address
// in the validation process
func ( m * Msg ) SetAddrHeaderIgnoreInvalid ( h AddrHeader , v ... string ) {
var al [ ] * mail . Address
for _ , av := range v {
2022-03-11 10:29:44 +01:00
a , err := mail . ParseAddress ( m . encodeString ( av ) )
2022-03-10 16:19:51 +01:00
if err != nil {
continue
}
al = append ( al , a )
}
m . addrHeader [ h ] = al
}
2022-06-13 10:18:35 +02:00
// EnvelopeFrom takes and validates a given mail address and sets it as envelope "FROM"
// addrHeader of the Msg
func ( m * Msg ) EnvelopeFrom ( f string ) error {
return m . SetAddrHeader ( HeaderEnvelopeFrom , f )
}
// EnvelopeFromFormat takes a name and address, formats them RFC5322 compliant and stores them as
// the envelope FROM address header field
func ( m * Msg ) EnvelopeFromFormat ( n , a string ) error {
return m . SetAddrHeader ( HeaderEnvelopeFrom , fmt . Sprintf ( ` "%s" <%s> ` , n , a ) )
}
2022-03-10 16:19:51 +01:00
// From takes and validates a given mail address and sets it as "From" genHeader of the Msg
func ( m * Msg ) From ( f string ) error {
return m . SetAddrHeader ( HeaderFrom , f )
2022-03-09 16:52:23 +01:00
}
2022-03-12 20:05:43 +01:00
// FromFormat takes a name and address, formats them RFC5322 compliant and stores them as
// the From address header field
func ( m * Msg ) FromFormat ( n , a string ) error {
return m . SetAddrHeader ( HeaderFrom , fmt . Sprintf ( ` "%s" <%s> ` , n , a ) )
}
2022-03-10 16:19:51 +01:00
// To takes and validates a given mail address list sets the To: addresses of the Msg
func ( m * Msg ) To ( t ... string ) error {
return m . SetAddrHeader ( HeaderTo , t ... )
2022-03-09 16:52:23 +01:00
}
2022-03-12 20:05:43 +01:00
// AddTo adds an additional address to the To address header field
func ( m * Msg ) AddTo ( t string ) error {
2022-03-13 10:49:07 +01:00
return m . addAddr ( HeaderTo , t )
2022-03-12 20:05:43 +01:00
}
2022-03-13 10:49:07 +01:00
// AddToFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional To address header field
func ( m * Msg ) AddToFormat ( n , a string ) error {
return m . addAddr ( HeaderTo , fmt . Sprintf ( ` "%s" <%s> ` , n , a ) )
2022-03-11 10:29:44 +01:00
}
2022-03-10 16:19:51 +01:00
// ToIgnoreInvalid takes and validates a given mail address list sets the To: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func ( m * Msg ) ToIgnoreInvalid ( t ... string ) {
m . SetAddrHeaderIgnoreInvalid ( HeaderTo , t ... )
2022-03-09 16:52:23 +01:00
}
2023-11-10 18:07:56 +01:00
// ToFromString takes and validates a given string of comma separted
// mail address and sets them as To: addresses of the Msg
func ( m * Msg ) ToFromString ( v string ) error {
return m . To ( strings . Split ( v , "," ) ... )
}
2022-03-10 16:19:51 +01:00
// Cc takes and validates a given mail address list sets the Cc: addresses of the Msg
func ( m * Msg ) Cc ( c ... string ) error {
return m . SetAddrHeader ( HeaderCc , c ... )
}
2022-03-13 10:49:07 +01:00
// AddCc adds an additional address to the Cc address header field
func ( m * Msg ) AddCc ( t string ) error {
return m . addAddr ( HeaderCc , t )
}
// AddCcFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional Cc address header field
func ( m * Msg ) AddCcFormat ( n , a string ) error {
return m . addAddr ( HeaderCc , fmt . Sprintf ( ` "%s" <%s> ` , n , a ) )
}
2022-03-10 16:19:51 +01:00
// CcIgnoreInvalid takes and validates a given mail address list sets the Cc: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func ( m * Msg ) CcIgnoreInvalid ( c ... string ) {
m . SetAddrHeaderIgnoreInvalid ( HeaderCc , c ... )
}
2023-11-10 18:07:56 +01:00
// CcFromString takes and validates a given string of comma separted
// mail address and sets them as Cc: addresses of the Msg
func ( m * Msg ) CcFromString ( v string ) error {
return m . Cc ( strings . Split ( v , "," ) ... )
}
2022-03-10 16:19:51 +01:00
// Bcc takes and validates a given mail address list sets the Bcc: addresses of the Msg
func ( m * Msg ) Bcc ( b ... string ) error {
return m . SetAddrHeader ( HeaderBcc , b ... )
}
2022-03-13 10:49:07 +01:00
// AddBcc adds an additional address to the Bcc address header field
func ( m * Msg ) AddBcc ( t string ) error {
return m . addAddr ( HeaderBcc , t )
}
// AddBccFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional Bcc address header field
func ( m * Msg ) AddBccFormat ( n , a string ) error {
return m . addAddr ( HeaderBcc , fmt . Sprintf ( ` "%s" <%s> ` , n , a ) )
}
2022-03-10 16:19:51 +01:00
// BccIgnoreInvalid takes and validates a given mail address list sets the Bcc: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func ( m * Msg ) BccIgnoreInvalid ( b ... string ) {
m . SetAddrHeaderIgnoreInvalid ( HeaderBcc , b ... )
2022-03-09 16:52:23 +01:00
}
2023-11-10 18:07:56 +01:00
// BccFromString takes and validates a given string of comma separted
// mail address and sets them as Bcc: addresses of the Msg
func ( m * Msg ) BccFromString ( v string ) error {
return m . Bcc ( strings . Split ( v , "," ) ... )
}
2022-03-13 19:36:24 +01:00
// ReplyTo takes and validates a given mail address and sets it as "Reply-To" addrHeader of the Msg
func ( m * Msg ) ReplyTo ( r string ) error {
2023-03-07 10:51:15 +01:00
rt , err := mail . ParseAddress ( r )
2022-03-13 19:36:24 +01:00
if err != nil {
return fmt . Errorf ( "failed to parse reply-to address: %w" , err )
}
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderReplyTo , rt . String ( ) )
2022-03-13 19:36:24 +01:00
return nil
}
// ReplyToFormat takes a name and address, formats them RFC5322 compliant and stores them as
// the Reply-To header field
func ( m * Msg ) ReplyToFormat ( n , a string ) error {
return m . ReplyTo ( fmt . Sprintf ( ` "%s" <%s> ` , n , a ) )
}
2022-03-13 10:49:07 +01:00
// addAddr adds an additional address to the given addrHeader of the Msg
func ( m * Msg ) addAddr ( h AddrHeader , a string ) error {
var al [ ] string
for _ , ca := range m . addrHeader [ h ] {
al = append ( al , ca . String ( ) )
}
al = append ( al , a )
return m . SetAddrHeader ( h , al ... )
}
// Subject sets the "Subject" header field of the Msg
func ( m * Msg ) Subject ( s string ) {
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderSubject , s )
2022-03-13 10:49:07 +01:00
}
2022-03-09 16:52:23 +01:00
// SetMessageID generates a random message id for the mail
func ( m * Msg ) SetMessageID ( ) {
hn , err := os . Hostname ( )
if err != nil {
hn = "localhost.localdomain"
}
2022-10-26 14:05:25 +02:00
rn , _ := randNum ( 100000000 )
rm , _ := randNum ( 10000 )
2022-10-26 15:48:48 +02:00
rs , _ := randomStringSecure ( 17 )
2022-10-26 14:05:25 +02:00
pid := os . Getpid ( ) * rm
mid := fmt . Sprintf ( "%d.%d%d.%s@%s" , pid , rn , rm , rs , hn )
2022-03-09 16:52:23 +01:00
m . SetMessageIDWithValue ( mid )
}
// SetMessageIDWithValue sets the message id for the mail
func ( m * Msg ) SetMessageIDWithValue ( v string ) {
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderMessageID , fmt . Sprintf ( "<%s>" , v ) )
2022-03-09 16:52:23 +01:00
}
2024-02-08 16:45:06 +01:00
// SetBulk sets the "Precedence: bulk" and "X-Auto-Response-Suppress: All" genHeaders which are
// recommended for automated mails like OOO replies
2022-03-09 16:52:23 +01:00
// See: https://www.rfc-editor.org/rfc/rfc2076#section-3.9
2024-02-08 16:45:06 +01:00
// See also: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/ced68690-498a-4567-9d14-5c01f974d8b1#Appendix_A_Target_51
2022-03-09 16:52:23 +01:00
func ( m * Msg ) SetBulk ( ) {
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderPrecedence , "bulk" )
2024-02-08 16:45:06 +01:00
m . SetGenHeader ( HeaderXAutoResponseSuppress , "All" )
2022-03-09 16:52:23 +01:00
}
2022-03-10 16:19:51 +01:00
// SetDate sets the Date genHeader field to the current time in a valid format
func ( m * Msg ) SetDate ( ) {
ts := time . Now ( ) . Format ( time . RFC1123Z )
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderDate , ts )
2022-03-10 16:19:51 +01:00
}
2022-03-14 11:50:36 +01:00
// SetDateWithValue sets the Date genHeader field to the provided time in a valid format
func ( m * Msg ) SetDateWithValue ( t time . Time ) {
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderDate , t . Format ( time . RFC1123Z ) )
2022-03-14 11:50:36 +01:00
}
2022-03-13 19:24:46 +01:00
// SetImportance sets the Msg Importance/Priority header to given Importance
func ( m * Msg ) SetImportance ( i Importance ) {
if i == ImportanceNormal {
return
}
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderImportance , i . String ( ) )
m . SetGenHeader ( HeaderPriority , i . NumString ( ) )
m . SetGenHeader ( HeaderXPriority , i . XPrioString ( ) )
m . SetGenHeader ( HeaderXMSMailPriority , i . NumString ( ) )
2022-03-13 19:24:46 +01:00
}
2022-03-14 16:25:24 +01:00
// SetOrganization sets the provided string as Organization header for the Msg
func ( m * Msg ) SetOrganization ( o string ) {
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderOrganization , o )
2022-03-14 16:25:24 +01:00
}
// SetUserAgent sets the User-Agent/X-Mailer header for the Msg
func ( m * Msg ) SetUserAgent ( a string ) {
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderUserAgent , a )
m . SetGenHeader ( HeaderXMailer , a )
2022-03-14 16:25:24 +01:00
}
2024-01-23 11:01:08 +01:00
// IsDelivered will return true if the Msg has been successfully delivered
func ( m * Msg ) IsDelivered ( ) bool {
return m . isDelivered
}
2022-09-09 11:35:45 +02:00
// RequestMDNTo adds the Disposition-Notification-To header to request a MDN from the receiving end
// as described in RFC8098. It allows to provide a list recipient addresses.
// Address validation is performed
// See: https://www.rfc-editor.org/rfc/rfc8098.html
func ( m * Msg ) RequestMDNTo ( t ... string ) error {
var tl [ ] string
for _ , at := range t {
a , err := mail . ParseAddress ( at )
if err != nil {
return fmt . Errorf ( errParseMailAddr , at , err )
}
tl = append ( tl , a . String ( ) )
}
2023-11-29 17:26:58 +01:00
if _ , ok := m . genHeader [ HeaderDispositionNotificationTo ] ; ok {
m . genHeader [ HeaderDispositionNotificationTo ] = tl
}
2022-09-09 11:35:45 +02:00
return nil
}
// RequestMDNToFormat adds the Disposition-Notification-To header to request a MDN from the receiving end
// as described in RFC8098. It allows to provide a recipient address with name and address and will format
// accordingly. Address validation is performed
// See: https://www.rfc-editor.org/rfc/rfc8098.html
func ( m * Msg ) RequestMDNToFormat ( n , a string ) error {
return m . RequestMDNTo ( fmt . Sprintf ( ` %s <%s> ` , n , a ) )
}
// RequestMDNAddTo adds an additional recipient to the recipient list of the MDN
func ( m * Msg ) RequestMDNAddTo ( t string ) error {
a , err := mail . ParseAddress ( t )
if err != nil {
return fmt . Errorf ( errParseMailAddr , t , err )
}
var tl [ ] string
2022-09-09 11:39:08 +02:00
tl = append ( tl , m . genHeader [ HeaderDispositionNotificationTo ] ... )
2022-09-09 11:35:45 +02:00
tl = append ( tl , a . String ( ) )
2023-11-29 17:26:58 +01:00
if _ , ok := m . genHeader [ HeaderDispositionNotificationTo ] ; ok {
m . genHeader [ HeaderDispositionNotificationTo ] = tl
}
2022-09-09 11:35:45 +02:00
return nil
}
// RequestMDNAddToFormat adds an additional formated recipient to the recipient list of the MDN
func ( m * Msg ) RequestMDNAddToFormat ( n , a string ) error {
return m . RequestMDNAddTo ( fmt . Sprintf ( ` "%s" <%s> ` , n , a ) )
}
2022-06-13 10:18:35 +02:00
// GetSender returns the currently set envelope FROM address. If no envelope FROM is set it will use
// the first mail body FROM address. If ff is true, it will return the full address string including
// the address name, if set
2022-03-12 15:10:01 +01:00
func ( m * Msg ) GetSender ( ff bool ) ( string , error ) {
2022-06-13 10:18:35 +02:00
f , ok := m . addrHeader [ HeaderEnvelopeFrom ]
2022-03-12 15:10:01 +01:00
if ! ok || len ( f ) == 0 {
2022-06-13 10:18:35 +02:00
f , ok = m . addrHeader [ HeaderFrom ]
if ! ok || len ( f ) == 0 {
return "" , ErrNoFromAddress
}
2022-03-11 16:57:14 +01:00
}
2022-03-12 15:10:01 +01:00
if ff {
return f [ 0 ] . String ( ) , nil
2022-03-11 16:57:14 +01:00
}
2022-03-12 15:10:01 +01:00
return f [ 0 ] . Address , nil
}
// GetRecipients returns a list of the currently set TO/CC/BCC addresses.
func ( m * Msg ) GetRecipients ( ) ( [ ] string , error ) {
var rl [ ] string
for _ , t := range [ ] AddrHeader { HeaderTo , HeaderCc , HeaderBcc } {
al , ok := m . addrHeader [ t ]
if ! ok || len ( al ) == 0 {
continue
}
for _ , r := range al {
rl = append ( rl , r . Address )
}
}
if len ( rl ) <= 0 {
return rl , ErrNoRcptAddresses
}
return rl , nil
}
2022-03-13 11:31:33 +01:00
2022-11-19 11:22:20 +01:00
// GetAddrHeader returns the content of the requested address header of the Msg
func ( m * Msg ) GetAddrHeader ( h AddrHeader ) [ ] * mail . Address {
return m . addrHeader [ h ]
}
// GetAddrHeaderString returns the address string of the requested address header of the Msg
func ( m * Msg ) GetAddrHeaderString ( h AddrHeader ) [ ] string {
var al [ ] string
2023-11-29 16:42:07 +01:00
for _ , mh := range m . addrHeader [ h ] {
al = append ( al , mh . String ( ) )
2022-11-19 11:22:20 +01:00
}
return al
}
// GetFrom returns the content of the From address header of the Msg
func ( m * Msg ) GetFrom ( ) [ ] * mail . Address {
return m . GetAddrHeader ( HeaderFrom )
}
// GetFromString returns the content of the From address header of the Msg as string slice
func ( m * Msg ) GetFromString ( ) [ ] string {
return m . GetAddrHeaderString ( HeaderFrom )
}
// GetTo returns the content of the To address header of the Msg
func ( m * Msg ) GetTo ( ) [ ] * mail . Address {
return m . GetAddrHeader ( HeaderTo )
}
// GetToString returns the content of the To address header of the Msg as string slice
func ( m * Msg ) GetToString ( ) [ ] string {
return m . GetAddrHeaderString ( HeaderTo )
}
// GetCc returns the content of the Cc address header of the Msg
func ( m * Msg ) GetCc ( ) [ ] * mail . Address {
return m . GetAddrHeader ( HeaderCc )
}
// GetCcString returns the content of the Cc address header of the Msg as string slice
func ( m * Msg ) GetCcString ( ) [ ] string {
return m . GetAddrHeaderString ( HeaderCc )
}
// GetBcc returns the content of the Bcc address header of the Msg
func ( m * Msg ) GetBcc ( ) [ ] * mail . Address {
return m . GetAddrHeader ( HeaderBcc )
}
// GetBccString returns the content of the Bcc address header of the Msg as string slice
func ( m * Msg ) GetBccString ( ) [ ] string {
return m . GetAddrHeaderString ( HeaderBcc )
}
2022-10-02 12:25:49 +02:00
// GetGenHeader returns the content of the requested generic header of the Msg
func ( m * Msg ) GetGenHeader ( h Header ) [ ] string {
return m . genHeader [ h ]
}
2022-10-11 17:05:44 +02:00
// GetParts returns the message parts of the Msg
func ( m * Msg ) GetParts ( ) [ ] * Part {
return m . parts
}
2022-10-12 13:36:52 +02:00
// GetAttachments returns the attachments of the Msg
func ( m * Msg ) GetAttachments ( ) [ ] * File {
return m . attachments
}
// SetAttachements sets the attachements of the message.
func ( m * Msg ) SetAttachements ( ff [ ] * File ) {
m . attachments = ff
}
2023-10-20 04:21:03 +02:00
// UnsetAllAttachments unset the attachments of the message.
func ( m * Msg ) UnsetAllAttachments ( ) {
m . attachments = nil
}
2023-01-29 13:48:51 +01:00
// GetEmbeds returns the embeds of the Msg
func ( m * Msg ) GetEmbeds ( ) [ ] * File {
return m . embeds
}
2023-10-20 04:21:03 +02:00
// SetEmbeds sets the embeds of the message.
2023-01-29 13:48:51 +01:00
func ( m * Msg ) SetEmbeds ( ff [ ] * File ) {
m . embeds = ff
}
2023-10-20 04:21:03 +02:00
// UnsetAllEmbeds unset the embeds of the message.
func ( m * Msg ) UnsetAllEmbeds ( ) {
m . embeds = nil
}
// UnsetAllParts unset the embeds and attachments of the message.
func ( m * Msg ) UnsetAllParts ( ) {
m . UnsetAllAttachments ( )
m . UnsetAllEmbeds ( )
}
2022-03-13 17:15:23 +01:00
// SetBodyString sets the body of the message.
func ( m * Msg ) SetBodyString ( ct ContentType , b string , o ... PartOption ) {
buf := bytes . NewBufferString ( b )
2022-06-03 10:40:54 +02:00
w := writeFuncFromBuffer ( buf )
2022-03-13 17:15:23 +01:00
m . SetBodyWriter ( ct , w , o ... )
}
// SetBodyWriter sets the body of the message.
2022-03-18 17:08:05 +01:00
func ( m * Msg ) SetBodyWriter ( ct ContentType , w func ( io . Writer ) ( int64 , error ) , o ... PartOption ) {
2022-03-14 10:29:53 +01:00
p := m . newPart ( ct , o ... )
2022-03-13 17:15:23 +01:00
p . w = w
m . parts = [ ] * Part { p }
}
2022-06-03 10:40:54 +02:00
// SetBodyHTMLTemplate sets the body of the message from a given html/template.Template pointer
// The content type will be set to text/html automatically
func ( m * Msg ) SetBodyHTMLTemplate ( t * ht . Template , d interface { } , o ... PartOption ) error {
2022-06-03 11:08:33 +02:00
if t == nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2022-06-03 10:40:54 +02:00
buf := bytes . Buffer { }
if err := t . Execute ( & buf , d ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
w := writeFuncFromBuffer ( & buf )
m . SetBodyWriter ( TypeTextHTML , w , o ... )
return nil
}
// SetBodyTextTemplate sets the body of the message from a given text/template.Template pointer
// The content type will be set to text/plain automatically
func ( m * Msg ) SetBodyTextTemplate ( t * tt . Template , d interface { } , o ... PartOption ) error {
2022-06-03 11:08:33 +02:00
if t == nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2022-06-03 10:40:54 +02:00
buf := bytes . Buffer { }
if err := t . Execute ( & buf , d ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
w := writeFuncFromBuffer ( & buf )
m . SetBodyWriter ( TypeTextPlain , w , o ... )
return nil
}
2022-03-13 17:15:23 +01:00
// AddAlternativeString sets the alternative body of the message.
func ( m * Msg ) AddAlternativeString ( ct ContentType , b string , o ... PartOption ) {
buf := bytes . NewBufferString ( b )
2022-06-03 10:40:54 +02:00
w := writeFuncFromBuffer ( buf )
2022-03-13 17:15:23 +01:00
m . AddAlternativeWriter ( ct , w , o ... )
}
// AddAlternativeWriter sets the body of the message.
2022-03-18 17:08:05 +01:00
func ( m * Msg ) AddAlternativeWriter ( ct ContentType , w func ( io . Writer ) ( int64 , error ) , o ... PartOption ) {
2022-03-14 10:29:53 +01:00
p := m . newPart ( ct , o ... )
2022-03-13 17:15:23 +01:00
p . w = w
m . parts = append ( m . parts , p )
}
2022-06-03 10:40:54 +02:00
// AddAlternativeHTMLTemplate sets the alternative body of the message to a html/template.Template output
// The content type will be set to text/html automatically
func ( m * Msg ) AddAlternativeHTMLTemplate ( t * ht . Template , d interface { } , o ... PartOption ) error {
2022-06-03 11:08:33 +02:00
if t == nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2022-06-03 10:40:54 +02:00
buf := bytes . Buffer { }
if err := t . Execute ( & buf , d ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
w := writeFuncFromBuffer ( & buf )
m . AddAlternativeWriter ( TypeTextHTML , w , o ... )
return nil
}
// AddAlternativeTextTemplate sets the alternative body of the message to a text/template.Template output
// The content type will be set to text/plain automatically
func ( m * Msg ) AddAlternativeTextTemplate ( t * tt . Template , d interface { } , o ... PartOption ) error {
2022-06-03 11:08:33 +02:00
if t == nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2022-06-03 10:40:54 +02:00
buf := bytes . Buffer { }
if err := t . Execute ( & buf , d ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
w := writeFuncFromBuffer ( & buf )
m . AddAlternativeWriter ( TypeTextPlain , w , o ... )
return nil
}
2022-03-14 10:29:53 +01:00
// AttachFile adds an attachment File to the Msg
func ( m * Msg ) AttachFile ( n string , o ... FileOption ) {
2022-03-15 11:56:21 +01:00
f := fileFromFS ( n )
if f == nil {
return
}
m . attachments = m . appendFile ( m . attachments , f , o ... )
2022-03-14 10:29:53 +01:00
}
// AttachReader adds an attachment File via io.Reader to the Msg
2023-01-31 17:48:19 +01:00
//
// CAVEAT: For AttachReader to work it has to read all data of the io.Reader
// into memory first, so it can seek through it. Using larger amounts of
2023-05-31 09:34:45 +02:00
// data on the io.Reader should be avoided. For such, it is recommended to
2023-01-31 17:48:19 +01:00
// either use AttachFile or AttachReadSeeker instead
2023-12-24 17:04:55 +01:00
func ( m * Msg ) AttachReader ( n string , r io . Reader , o ... FileOption ) error {
f , err := fileFromReader ( n , r )
if err != nil {
return err
}
2022-03-15 11:56:21 +01:00
m . attachments = m . appendFile ( m . attachments , f , o ... )
2023-12-24 17:04:55 +01:00
return nil
2022-03-14 10:29:53 +01:00
}
2023-01-31 17:38:31 +01:00
// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg
func ( m * Msg ) AttachReadSeeker ( n string , r io . ReadSeeker , o ... FileOption ) {
f := fileFromReadSeeker ( n , r )
m . attachments = m . appendFile ( m . attachments , f , o ... )
}
2022-06-03 10:40:54 +02:00
// 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 {
f , err := fileFromHTMLTemplate ( n , t , d )
if err != nil {
return fmt . Errorf ( "failed to attach template: %w" , err )
}
m . attachments = m . appendFile ( m . attachments , f , o ... )
return nil
}
// 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 {
f , err := fileFromTextTemplate ( n , t , d )
2022-06-01 16:49:34 +02:00
if err != nil {
return fmt . Errorf ( "failed to attach template: %w" , err )
}
m . attachments = m . appendFile ( m . attachments , f , o ... )
return nil
}
2022-07-07 10:46:57 +02:00
// AttachFromEmbedFS adds an attachment File from an embed.FS to the Msg
func ( m * Msg ) AttachFromEmbedFS ( n string , f * embed . FS , o ... FileOption ) error {
if f == nil {
return fmt . Errorf ( "embed.FS must not be nil" )
}
ef , err := fileFromEmbedFS ( n , f )
if err != nil {
return err
}
m . attachments = m . appendFile ( m . attachments , ef , o ... )
return nil
}
2022-03-14 10:29:53 +01:00
// EmbedFile adds an embedded File to the Msg
func ( m * Msg ) EmbedFile ( n string , o ... FileOption ) {
2022-03-15 11:56:21 +01:00
f := fileFromFS ( n )
if f == nil {
return
}
m . embeds = m . appendFile ( m . embeds , f , o ... )
2022-03-14 10:29:53 +01:00
}
// EmbedReader adds an embedded File from an io.Reader to the Msg
2023-01-31 17:48:19 +01:00
//
2023-01-31 21:57:10 +01:00
// CAVEAT: For EmbedReader to work it has to read all data of the io.Reader
2023-01-31 17:48:19 +01:00
// into memory first, so it can seek through it. Using larger amounts of
2023-05-31 09:34:45 +02:00
// data on the io.Reader should be avoided. For such, it is recommended to
2023-01-31 21:57:10 +01:00
// either use EmbedFile or EmbedReadSeeker instead
2023-12-24 17:04:55 +01:00
func ( m * Msg ) EmbedReader ( n string , r io . Reader , o ... FileOption ) error {
f , err := fileFromReader ( n , r )
if err != nil {
return err
}
2022-03-15 11:56:21 +01:00
m . embeds = m . appendFile ( m . embeds , f , o ... )
2023-12-24 17:04:55 +01:00
return nil
2022-03-14 10:29:53 +01:00
}
2023-01-31 17:38:31 +01:00
// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg
func ( m * Msg ) EmbedReadSeeker ( n string , r io . ReadSeeker , o ... FileOption ) {
f := fileFromReadSeeker ( n , r )
m . embeds = m . appendFile ( m . embeds , f , o ... )
}
2022-06-03 10:40:54 +02:00
// 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 {
f , err := fileFromHTMLTemplate ( n , t , d )
if err != nil {
return fmt . Errorf ( "failed to embed template: %w" , err )
}
m . embeds = m . appendFile ( m . embeds , f , o ... )
return nil
}
// 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 {
f , err := fileFromTextTemplate ( n , t , d )
2022-06-01 16:49:34 +02:00
if err != nil {
return fmt . Errorf ( "failed to embed template: %w" , err )
}
m . embeds = m . appendFile ( m . embeds , f , o ... )
return nil
}
2022-07-07 10:46:57 +02:00
// EmbedFromEmbedFS adds an embedded File from an embed.FS to the Msg
func ( m * Msg ) EmbedFromEmbedFS ( n string , f * embed . FS , o ... FileOption ) error {
if f == nil {
return fmt . Errorf ( "embed.FS must not be nil" )
}
ef , err := fileFromEmbedFS ( n , f )
if err != nil {
return err
}
m . embeds = m . appendFile ( m . embeds , ef , o ... )
return nil
}
2022-03-14 11:50:36 +01:00
// Reset resets all headers, body parts and attachments/embeds of the Msg
// It leaves already set encodings, charsets, boundaries, etc. as is
func ( m * Msg ) Reset ( ) {
m . addrHeader = make ( map [ AddrHeader ] [ ] * mail . Address )
m . attachments = nil
m . embeds = nil
m . genHeader = make ( map [ Header ] [ ] string )
m . parts = nil
}
2022-09-22 18:05:47 +02:00
// ApplyMiddlewares apply the list of middlewares to a Msg
func ( m * Msg ) applyMiddlewares ( ms * Msg ) * Msg {
for _ , mw := range m . middlewares {
ms = mw . Handle ( ms )
}
return ms
}
2022-05-24 15:46:59 +02:00
// 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 ) {
2022-03-15 12:12:45 +01:00
mw := & msgWriter { w : w , c : m . charset , en : m . encoder }
2022-09-22 18:05:47 +02:00
mw . writeMsg ( m . applyMiddlewares ( m ) )
2022-03-12 15:10:01 +01:00
return mw . n , mw . err
2022-03-09 16:52:23 +01:00
}
2022-03-11 10:29:44 +01:00
2022-10-25 16:42:18 +02:00
// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies
// the io.WriteTo interface but will skip the given Middleware
func ( m * Msg ) WriteToSkipMiddleware ( w io . Writer , mt MiddlewareType ) ( int64 , error ) {
var omwl , mwl [ ] Middleware
2022-10-25 16:56:40 +02:00
omwl = m . middlewares
2022-10-25 16:42:18 +02:00
for i := range m . middlewares {
if m . middlewares [ i ] . Type ( ) == mt {
continue
}
mwl = append ( mwl , m . middlewares [ i ] )
}
m . middlewares = mwl
mw := & msgWriter { w : w , c : m . charset , en : m . encoder }
mw . writeMsg ( m . applyMiddlewares ( m ) )
m . middlewares = omwl
return mw . n , mw . err
}
2022-06-02 16:22:48 +02:00
// Write is an alias method to WriteTo due to compatibility reasons
2022-05-24 15:46:59 +02:00
func ( m * Msg ) Write ( w io . Writer ) ( int64 , error ) {
return m . WriteTo ( w )
}
2022-03-14 10:29:53 +01:00
// appendFile adds a File to the Msg (as attachment or embed)
func ( m * Msg ) appendFile ( c [ ] * File , f * File , o ... FileOption ) [ ] * File {
// Override defaults with optionally provided FileOption functions
for _ , co := range o {
if co == nil {
continue
}
co ( f )
}
if c == nil {
return [ ] * File { f }
}
return append ( c , f )
}
2022-06-06 16:43:04 +02:00
// WriteToFile stores the Msg as file on disk. It will try to create the given filename
// Already existing files will be overwritten
func ( m * Msg ) WriteToFile ( n string ) error {
f , err := os . Create ( n )
if err != nil {
return fmt . Errorf ( "failed to create output file: %w" , err )
}
defer func ( ) { _ = f . Close ( ) } ( )
_ , err = m . WriteTo ( f )
if err != nil {
return fmt . Errorf ( "failed to write to output file: %w" , err )
}
return f . Close ( )
}
2022-03-18 21:07:07 +01:00
// WriteToSendmail returns WriteToSendmailWithCommand with a default sendmail path
2022-03-14 15:26:53 +01:00
func ( m * Msg ) WriteToSendmail ( ) error {
2022-03-18 21:07:07 +01:00
return m . WriteToSendmailWithCommand ( SendmailPath )
}
// WriteToSendmailWithCommand returns WriteToSendmailWithContext with a default timeout
// of 5 seconds and a given sendmail path
func ( m * Msg ) WriteToSendmailWithCommand ( sp string ) error {
2022-03-14 15:26:53 +01:00
tctx , tcfn := context . WithTimeout ( context . Background ( ) , time . Second * 5 )
defer tcfn ( )
2022-03-18 21:07:07 +01:00
return m . WriteToSendmailWithContext ( tctx , sp )
2022-03-14 15:26:53 +01:00
}
// 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
// arguments for the sendmail binary as parameters
func ( m * Msg ) WriteToSendmailWithContext ( ctx context . Context , sp string , a ... string ) error {
ec := exec . CommandContext ( ctx , sp )
ec . Args = append ( ec . Args , "-oi" , "-t" )
ec . Args = append ( ec . Args , a ... )
2022-03-18 22:23:03 +01:00
se , err := ec . StderrPipe ( )
if err != nil {
return fmt . Errorf ( "failed to set STDERR pipe: %w" , err )
}
2022-03-14 15:26:53 +01:00
si , err := ec . StdinPipe ( )
if err != nil {
return fmt . Errorf ( "failed to set STDIN pipe: %w" , err )
}
2023-11-29 16:43:50 +01:00
if se == nil || si == nil {
return fmt . Errorf ( "received nil for STDERR or STDIN pipe" )
}
2022-03-14 15:26:53 +01:00
// Start the execution and write to STDIN
2023-11-20 18:36:19 +01:00
if err = ec . Start ( ) ; err != nil {
2022-03-14 15:26:53 +01:00
return fmt . Errorf ( "could not start sendmail execution: %w" , err )
}
2022-05-24 15:46:59 +02:00
_ , err = m . WriteTo ( si )
2022-03-14 15:26:53 +01:00
if err != nil {
2022-03-18 23:28:11 +01:00
if ! errors . Is ( err , syscall . EPIPE ) {
return fmt . Errorf ( "failed to write mail to buffer: %w" , err )
}
2022-03-14 15:26:53 +01:00
}
2022-05-26 13:37:23 +02:00
// Close STDIN and wait for completion or cancellation of the sendmail executable
2023-11-20 18:36:19 +01:00
if err = si . Close ( ) ; err != nil {
2022-05-26 13:37:23 +02:00
return fmt . Errorf ( "failed to close STDIN pipe: %w" , err )
}
2022-03-18 22:23:03 +01:00
// Read the stderr pipe for possible errors
2022-05-26 13:37:23 +02:00
serr , err := io . ReadAll ( se )
2022-03-18 22:23:03 +01:00
if err != nil {
2022-03-14 15:26:53 +01:00
return fmt . Errorf ( "failed to read STDERR pipe: %w" , err )
}
2022-05-26 13:37:23 +02:00
if len ( serr ) > 0 {
return fmt . Errorf ( "sendmail command failed: %s" , string ( serr ) )
2022-03-14 15:26:53 +01:00
}
2023-11-20 18:36:19 +01:00
if err = ec . Wait ( ) ; err != nil {
2022-03-14 15:26:53 +01:00
return fmt . Errorf ( "sendmail command execution failed: %w" , err )
}
return nil
}
2022-10-20 18:03:57 +02:00
// NewReader returns a Reader type that satisfies the io.Reader interface.
//
// IMPORTANT: when creating a new Reader, the current state of the Msg is taken, as
// basis for the Reader. If you perform changes on Msg after creating the Reader, these
// 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
func ( m * Msg ) NewReader ( ) * Reader {
r := & Reader { }
2022-10-20 15:52:53 +02:00
wbuf := bytes . Buffer { }
2022-10-20 18:03:57 +02:00
_ , err := m . Write ( & wbuf )
if err != nil {
r . err = fmt . Errorf ( "failed to write Msg to Reader buffer: %w" , err )
}
r . buf = wbuf . Bytes ( )
2022-10-20 15:52:53 +02:00
return r
}
2022-10-20 18:03:57 +02:00
// UpdateReader will update a Reader with the content of the given Msg and reset the
// Reader position to the start
func ( m * Msg ) UpdateReader ( r * Reader ) {
2022-10-20 15:52:53 +02:00
wbuf := bytes . Buffer { }
2022-10-20 18:03:57 +02:00
_ , err := m . Write ( & wbuf )
2022-10-20 15:52:53 +02:00
r . Reset ( )
r . buf = wbuf . Bytes ( )
2022-10-20 18:03:57 +02:00
r . err = err
2022-10-20 15:52:53 +02:00
}
2022-12-31 12:40:42 +01:00
// HasSendError returns true if the Msg experienced an error during the message delivery and the
2023-01-02 22:28:39 +01:00
// sendError field of the Msg is not nil
2022-12-31 12:40:42 +01:00
func ( m * Msg ) HasSendError ( ) bool {
return m . sendError != nil
}
2023-01-01 14:20:13 +01:00
// 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
func ( m * Msg ) SendErrorIsTemp ( ) bool {
2023-01-01 14:25:00 +01:00
var e * SendError
2023-11-29 16:45:25 +01:00
if errors . As ( m . sendError , & e ) && e != nil {
2023-01-01 14:25:00 +01:00
return e . isTemp
2023-01-01 14:20:13 +01:00
}
2023-01-01 14:25:00 +01:00
return false
2023-01-01 14:20:13 +01:00
}
2023-01-02 22:29:10 +01:00
// SendError returns the sendError field of the Msg
2022-12-31 12:40:42 +01:00
func ( m * Msg ) SendError ( ) error {
return m . sendError
}
2022-03-14 10:29:53 +01:00
// encodeString encodes a string based on the configured message encoder and the corresponding
// charset for the Msg
func ( m * Msg ) encodeString ( s string ) string {
return m . encoder . Encode ( string ( m . charset ) , s )
}
// hasAlt returns true if the Msg has more than one part
func ( m * Msg ) hasAlt ( ) bool {
2023-01-28 14:39:14 +01:00
c := 0
for _ , p := range m . parts {
if ! p . del {
c ++
}
}
2023-01-31 18:35:48 +01:00
return c > 1 && m . pgptype == 0
2022-03-14 10:29:53 +01:00
}
// hasMixed returns true if the Msg has mixed parts
func ( m * Msg ) hasMixed ( ) bool {
2023-01-31 18:35:48 +01:00
return m . pgptype == 0 && ( ( len ( m . parts ) > 0 && len ( m . attachments ) > 0 ) || len ( m . attachments ) > 1 )
2022-03-14 10:29:53 +01:00
}
// hasRelated returns true if the Msg has related parts
func ( m * Msg ) hasRelated ( ) bool {
2023-01-31 18:35:48 +01:00
return m . pgptype == 0 && ( ( len ( m . parts ) > 0 && len ( m . embeds ) > 0 ) || len ( m . embeds ) > 1 )
}
// hasPGPType returns true if the Msg should be treated as PGP encoded message
func ( m * Msg ) hasPGPType ( ) bool {
return m . pgptype > 0
2022-03-14 10:29:53 +01:00
}
// newPart returns a new Part for the Msg
func ( m * Msg ) newPart ( ct ContentType , o ... PartOption ) * Part {
2022-03-13 17:15:23 +01:00
p := & Part {
ctype : ct ,
2024-01-31 16:28:41 +01:00
cset : m . charset ,
2022-03-13 17:15:23 +01:00
enc : m . encoding ,
}
// Override defaults with optionally provided MsgOption functions
for _ , co := range o {
if co == nil {
continue
}
co ( p )
}
return p
}
2022-03-14 10:29:53 +01:00
// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message
func ( m * Msg ) setEncoder ( ) {
m . encoder = getEncoder ( m . encoding )
2022-03-13 17:15:23 +01:00
}
2022-03-20 17:38:46 +01:00
// checkUserAgent checks if a useragent/x-mailer is set and if not will set a default
// version string
func ( m * Msg ) checkUserAgent ( ) {
_ , uaok := m . genHeader [ HeaderUserAgent ]
_ , xmok := m . genHeader [ HeaderXMailer ]
if ! uaok && ! xmok {
m . SetUserAgent ( fmt . Sprintf ( "go-mail v%s // https://github.com/wneessen/go-mail" ,
VERSION ) )
}
}
2022-03-20 19:11:58 +01:00
// addDefaultHeader sets some default headers, if they haven't been set before
func ( m * Msg ) addDefaultHeader ( ) {
if _ , ok := m . genHeader [ HeaderDate ] ; ! ok {
m . SetDate ( )
}
if _ , ok := m . genHeader [ HeaderMessageID ] ; ! ok {
m . SetMessageID ( )
}
2022-11-19 11:22:20 +01:00
m . SetGenHeader ( HeaderMIMEVersion , string ( m . mimever ) )
2022-03-20 19:11:58 +01:00
}
2022-07-07 10:46:57 +02:00
// fileFromEmbedFS returns a File pointer from a given file in the provided embed.FS
func fileFromEmbedFS ( n string , f * embed . FS ) ( * File , error ) {
_ , err := f . Open ( n )
if err != nil {
return nil , fmt . Errorf ( "failed to open file from embed.FS: %w" , err )
}
return & File {
Name : filepath . Base ( n ) ,
Header : make ( map [ string ] [ ] string ) ,
Writer : func ( w io . Writer ) ( int64 , error ) {
h , err := f . Open ( n )
if err != nil {
return 0 , err
}
nb , err := io . Copy ( w , h )
if err != nil {
_ = h . Close ( )
return nb , fmt . Errorf ( "failed to copy file to io.Writer: %w" , err )
}
return nb , h . Close ( )
} ,
} , nil
}
2022-03-14 10:29:53 +01:00
// fileFromFS returns a File pointer from a given file in the system's file system
func fileFromFS ( n string ) * File {
2022-03-15 11:56:21 +01:00
_ , err := os . Stat ( n )
if err != nil {
return nil
}
2022-03-14 10:29:53 +01:00
return & File {
Name : filepath . Base ( n ) ,
Header : make ( map [ string ] [ ] string ) ,
2022-03-18 17:08:05 +01:00
Writer : func ( w io . Writer ) ( int64 , error ) {
2022-03-14 10:29:53 +01:00
h , err := os . Open ( n )
if err != nil {
2022-03-18 17:08:05 +01:00
return 0 , err
2022-03-14 10:29:53 +01:00
}
2022-03-18 17:08:05 +01:00
nb , err := io . Copy ( w , h )
if err != nil {
2022-03-14 10:29:53 +01:00
_ = h . Close ( )
2022-03-18 17:08:05 +01:00
return nb , fmt . Errorf ( "failed to copy file to io.Writer: %w" , err )
2022-03-14 10:29:53 +01:00
}
2022-03-18 17:08:05 +01:00
return nb , h . Close ( )
2022-03-14 10:29:53 +01:00
} ,
}
2022-03-13 17:15:23 +01:00
}
2022-03-14 10:29:53 +01:00
// fileFromReader returns a File pointer from a given io.Reader
2023-12-24 17:04:55 +01:00
func fileFromReader ( n string , r io . Reader ) ( * File , error ) {
2023-01-31 17:38:31 +01:00
d , err := io . ReadAll ( r )
if err != nil {
2023-12-24 17:04:55 +01:00
return & File { } , err
2023-01-31 17:38:31 +01:00
}
br := bytes . NewReader ( d )
2022-03-14 10:29:53 +01:00
return & File {
2023-01-31 17:38:31 +01:00
Name : n ,
Header : make ( map [ string ] [ ] string ) ,
Writer : func ( w io . Writer ) ( int64 , error ) {
rb , cerr := io . Copy ( w , br )
if cerr != nil {
return rb , cerr
}
_ , cerr = br . Seek ( 0 , io . SeekStart )
return rb , cerr
} ,
2023-12-24 17:04:55 +01:00
} , nil
2023-01-31 17:38:31 +01:00
}
// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker
func fileFromReadSeeker ( n string , r io . ReadSeeker ) * File {
return & File {
Name : n ,
2022-03-14 10:29:53 +01:00
Header : make ( map [ string ] [ ] string ) ,
2022-03-18 17:08:05 +01:00
Writer : func ( w io . Writer ) ( int64 , error ) {
2023-01-31 17:38:31 +01:00
rb , err := io . Copy ( w , r )
2022-03-18 17:08:05 +01:00
if err != nil {
2023-01-31 17:38:31 +01:00
return rb , err
2022-03-14 10:29:53 +01:00
}
2023-01-31 17:38:31 +01:00
_ , err = r . Seek ( 0 , io . SeekStart )
return rb , err
2022-03-14 10:29:53 +01:00
} ,
}
2022-03-13 17:15:23 +01:00
}
2022-06-03 10:40:54 +02:00
// fileFromHTMLTemplate returns a File pointer form a given html/template.Template
func fileFromHTMLTemplate ( n string , t * ht . Template , d interface { } ) ( * File , error ) {
2022-06-03 11:08:33 +02:00
if t == nil {
2022-06-03 12:27:26 +02:00
return nil , fmt . Errorf ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2022-06-03 10:40:54 +02:00
buf := bytes . Buffer { }
if err := t . Execute ( & buf , d ) ; err != nil {
2022-06-03 12:27:26 +02:00
return nil , fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
2023-12-24 17:04:55 +01:00
return fileFromReader ( n , & buf )
2022-06-03 10:40:54 +02:00
}
// fileFromTextTemplate returns a File pointer form a given text/template.Template
func fileFromTextTemplate ( n string , t * tt . Template , d interface { } ) ( * File , error ) {
2022-06-03 11:08:33 +02:00
if t == nil {
2022-06-03 12:27:26 +02:00
return nil , fmt . Errorf ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2022-06-01 16:49:34 +02:00
buf := bytes . Buffer { }
if err := t . Execute ( & buf , d ) ; err != nil {
2022-06-03 12:27:26 +02:00
return nil , fmt . Errorf ( errTplExecuteFailed , err )
2022-06-01 16:49:34 +02:00
}
2023-12-24 17:04:55 +01:00
return fileFromReader ( n , & buf )
2022-06-01 16:49:34 +02:00
}
2022-03-13 17:15:23 +01:00
// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message
func getEncoder ( e Encoding ) mime . WordEncoder {
switch e {
2022-03-11 10:29:44 +01:00
case EncodingQP :
2022-03-13 17:15:23 +01:00
return mime . QEncoding
2022-03-11 10:29:44 +01:00
case EncodingB64 :
2022-03-13 17:15:23 +01:00
return mime . BEncoding
2022-03-11 10:29:44 +01:00
default :
2022-03-13 17:15:23 +01:00
return mime . QEncoding
2022-03-11 10:29:44 +01:00
}
}
2022-06-03 10:40:54 +02:00
// writeFuncFromBuffer is a common method to convert a byte buffer into a writeFunc as
// often required by this library
func writeFuncFromBuffer ( buf * bytes . Buffer ) func ( io . Writer ) ( int64 , error ) {
w := func ( w io . Writer ) ( int64 , error ) {
nb , err := w . Write ( buf . Bytes ( ) )
return int64 ( nb ) , err
}
return w
}