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 (
2024-10-05 12:15:01 +02:00
// ErrNoFromAddress indicates that the FROM address is not set, which is required.
2022-03-12 15:10:01 +01:00
ErrNoFromAddress = errors . New ( "no FROM address set" )
2024-10-05 12:15:01 +02:00
// ErrNoRcptAddresses indicates that no recipient addresses have been set.
2022-03-12 15:10:01 +01:00
ErrNoRcptAddresses = errors . New ( "no recipient addresses set" )
)
2022-06-03 12:27:26 +02:00
const (
2024-10-05 12:15:01 +02:00
// errTplExecuteFailed indicates that the execution of a template has failed, including the underlying error.
2022-06-03 12:27:26 +02:00
errTplExecuteFailed = "failed to execute template: %w"
2024-10-05 12:15:01 +02:00
// errTplPointerNil indicates that a template pointer is nil, which prevents further template execution or
// processing.
2022-06-03 12:27:26 +02:00
errTplPointerNil = "template pointer is nil"
2022-09-09 11:35:45 +02:00
2024-10-05 12:15:01 +02:00
// errParseMailAddr indicates that parsing of a mail address has failed, including the problematic address
// and error.
2022-09-09 11:35:45 +02:00
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 (
2024-10-05 12:15:01 +02:00
// NoPGP indicates that a message should not be treated as PGP encrypted or signed and is the default value
// for a message
2023-01-31 18:35:48 +01:00
NoPGP PGPType = iota
2024-10-05 12:15:01 +02:00
// PGPEncrypt indicates that a message should be treated as PGP encrypted. This works closely together with
// the corresponding go-mail-middleware.
2023-01-31 18:35:48 +01:00
PGPEncrypt
2024-10-05 12:15:01 +02:00
// PGPSignature indicates that a message should be treated as PGP signed. This works closely together with
// the corresponding go-mail-middleware.
2023-01-31 18:35:48 +01:00
PGPSignature
)
2024-10-05 12:15:01 +02:00
// MiddlewareType is a type wrapper for a string. It describes the type of the Middleware and needs to be
// returned by the Middleware.Type method to satisfy the Middleware interface.
2022-10-25 16:42:18 +02:00
type MiddlewareType string
2024-10-05 12:15:01 +02:00
// Middleware represents the interface for modifying or handling email messages. A Middleware allows the user to
// alter a Msg before it is finally processed. Multiple Middleware can be applied to a Msg.
//
// Type returns a unique MiddlewareType. It describes the type of Middleware and makes sure that
// a Middleware is only applied once.
// Handle performs all the processing to the Msg. It always needs to return a Msg back.
2022-09-22 18:05:47 +02:00
type Middleware interface {
Handle ( * Msg ) * Msg
2022-10-25 16:42:18 +02:00
Type ( ) MiddlewareType
2022-09-22 18:05:47 +02:00
}
2024-10-05 12:15:01 +02:00
// PGPType is a type wrapper for an int, representing a type of PGP encryption or signature.
2023-01-31 18:35:48 +01:00
type PGPType int
2024-10-05 12:15:01 +02:00
// Msg represents an email message with various headers, attachments, and encoding settings.
//
// The Msg is the central part of go-mail. It provided a lot of methods that you would expect in a mail
// user agent (MUA). Msg satisfies the io.WriterTo and io.Reader interfaces.
2022-03-09 16:52:23 +01:00
type Msg struct {
2024-10-05 12:35:15 +02:00
// addrHeader holds a mapping between AddrHeader keys and their corresponding slices of mail.Address pointers.
2022-03-13 17:15:23 +01:00
addrHeader map [ AddrHeader ] [ ] * mail . Address
2024-10-05 12:35:15 +02:00
// attachments holds a list of File pointers that represent files either as attachments or embeds files in
// a Msg.
2022-03-18 22:23:03 +01:00
attachments [ ] * File
2024-10-05 12:35:15 +02:00
// boundary represents the delimiter for separating parts in a multipart message.
2022-03-13 17:15:23 +01:00
boundary string
2024-10-05 12:35:15 +02:00
// charset represents the Charset of the Msg.
//
// By default we set CharsetUTF8 for a Msg unless overridden by a corresponding MsgOption.
2022-03-11 10:29:44 +01:00
charset Charset
2024-10-05 12:35:15 +02:00
// embeds contains a slice of File pointers representing the embedded files in a Msg.
2022-03-18 22:23:03 +01:00
embeds [ ] * File
2022-03-09 16:52:23 +01:00
2024-10-05 12:35:15 +02:00
// encoder is a mime.WordEncoder used to encode strings (such as email headers) using a specified
// Encoding.
2022-03-11 10:29:44 +01:00
encoder mime . WordEncoder
2022-03-09 16:52:23 +01:00
2024-10-05 12:35:15 +02:00
// encoding specifies the type of Encoding used for email messages and/or parts.
2022-03-18 22:23:03 +01:00
encoding Encoding
2024-10-05 12:35:15 +02:00
// genHeader is a map where the keys are email headers (of type Header) and the values are slices of strings
// representing header values.
2022-03-10 16:19:51 +01:00
genHeader map [ Header ] [ ] string
2024-10-05 12:35:15 +02:00
// isDelivered indicates wether the Msg has been delivered.
2024-01-23 11:01:08 +01:00
isDelivered bool
2024-10-05 12:35:15 +02:00
// middlewares is a slice of Middleware used for modifying or handling messages before they are processed.
//
// middlewares are processed in FIFO order.
2023-01-31 18:35:48 +01:00
middlewares [ ] Middleware
2022-10-26 15:33:03 +02:00
2024-10-05 12:35:15 +02:00
// mimever represents the MIME version used in a Msg.
2022-03-13 17:15:23 +01:00
mimever MIMEVersion
2024-10-05 12:35:15 +02:00
// parts is a slice that holds pointers to Part structures, which represent different parts of a Msg.
2022-03-13 17:15:23 +01:00
parts [ ] * Part
2022-09-22 18:05:47 +02:00
2024-10-05 12:35:15 +02:00
// preformHeader maps Header types to their already preformatted string values.
//
// Preformatted Header values will not be affected by automatic line breaks.
2023-01-31 18:35:48 +01:00
preformHeader map [ Header ] string
// pgptype indicates that a message has a PGPType assigned and therefore will generate
2024-10-05 12:35:15 +02:00
// different Content-Type settings in the msgWriter.
2023-01-31 18:35:48 +01:00
pgptype PGPType
2022-12-31 12:40:42 +01:00
2024-10-05 12:35:15 +02:00
// sendError represents an error encountered during the process of sending a Msg during the
// Client.Send operation.
//
// sendError will hold an error of type SendError.
2022-12-31 12:40:42 +01:00
sendError error
2024-02-23 23:41:58 +01:00
2024-10-05 12:35:15 +02:00
// noDefaultUserAgent indicates whether the default User-Agent will be omitted for the Msg when it is
// being sent.
//
// This can be useful in scenarios where headers are conditionally passed based on receipt - i. e. SMTP proxies.
2024-02-23 23:41:58 +01:00
noDefaultUserAgent bool
2022-03-14 10:29:53 +01:00
}
2022-03-13 17:15:23 +01:00
2024-10-05 12:48:09 +02:00
// SendmailPath is the default system path to the sendmail binary - at least on standard Unix-like OS.
2022-05-25 10:53:37 +02:00
const SendmailPath = "/usr/sbin/sendmail"
2022-03-14 15:26:53 +01:00
2024-10-05 12:48:09 +02:00
// MsgOption is a function type that modifies a Msg instance during its creation or initialization.
2022-03-11 10:29:44 +01:00
type MsgOption func ( * Msg )
2024-10-05 12:48:09 +02:00
// NewMsg creates a new email message with optional MsgOption functions that customize various aspects of the
// message.
2024-02-24 18:26:30 +01:00
func NewMsg ( opts ... MsgOption ) * Msg {
msg := & 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 ) ,
2023-10-13 15:23:28 +02:00
mimever : MIME10 ,
2022-03-09 16:52:23 +01:00
}
2022-03-11 10:29:44 +01:00
2024-10-05 12:48:09 +02:00
// Override defaults with optionally provided MsgOption functions.
2024-02-24 18:26:30 +01:00
for _ , option := range opts {
if option == nil {
2022-03-11 10:29:44 +01:00
continue
}
2024-02-24 18:26:30 +01:00
option ( msg )
2022-03-11 10:29:44 +01:00
}
// Set the matcing mime.WordEncoder for the Msg
2024-02-24 18:26:30 +01:00
msg . setEncoder ( )
2022-03-11 10:29:44 +01:00
2024-02-24 18:26:30 +01:00
return msg
2022-03-09 16:52:23 +01:00
}
2024-10-05 12:48:09 +02:00
// WithCharset sets the Charset type for a Msg during its creation or initialization.
2022-03-11 10:29:44 +01:00
func WithCharset ( c Charset ) MsgOption {
return func ( m * Msg ) {
m . charset = c
}
}
2024-10-05 12:48:09 +02:00
// WithEncoding sets the Encoding type for a Msg during its creation or initialization.
2022-03-11 10:29:44 +01:00
func WithEncoding ( e Encoding ) MsgOption {
return func ( m * Msg ) {
m . encoding = e
}
}
2024-10-05 12:48:09 +02:00
// WithMIMEVersion sets the MIMEVersion type for a Msg during its creation or initialization.
//
// Note that in the context of email, MIME Version 1.0 is the only officially standardized and supported
// version. While MIME has been updated and extended over time (via various RFCs), these updates and extensions
// do not introduce new MIME versions; they refine or add features within the framework of MIME 1.0.
// Therefore there should be no reason to ever use this MsgOption.
// https://datatracker.ietf.org/doc/html/rfc1521
2024-10-05 13:39:21 +02:00
//
2024-10-05 12:48:09 +02:00
// https://datatracker.ietf.org/doc/html/rfc2045
2024-10-05 13:39:21 +02:00
//
2024-10-05 12:48:09 +02:00
// https://datatracker.ietf.org/doc/html/rfc2049
2022-03-13 17:15:23 +01:00
func WithMIMEVersion ( mv MIMEVersion ) MsgOption {
return func ( m * Msg ) {
m . mimever = mv
}
}
2024-10-05 12:48:09 +02:00
// WithBoundary sets the boundary of a Msg to the provided string value during its creation or initialization.
//
// Note that by default we create random MIME boundaries. This should only be used if a specific boundary is
// required.
2022-03-18 11:47:50 +01:00
func WithBoundary ( b string ) MsgOption {
return func ( m * Msg ) {
m . boundary = b
}
}
2024-10-05 12:48:09 +02:00
// WithMiddleware adds the given Middleware to the end of the list of the Client middlewares slice. Middleware
// are processed in FIFO order.
2022-09-22 18:05:47 +02:00
func WithMiddleware ( mw Middleware ) MsgOption {
return func ( m * Msg ) {
m . middlewares = append ( m . middlewares , mw )
}
}
2024-10-05 12:48:09 +02:00
// WithPGPType sets the PGP type for the Msg during its creation or initialization, determining the encryption or
// signature method.
2024-02-24 18:26:30 +01:00
func WithPGPType ( pt PGPType ) MsgOption {
2023-01-31 18:35:48 +01:00
return func ( m * Msg ) {
2024-02-24 18:26:30 +01:00
m . pgptype = pt
2023-01-31 18:35:48 +01:00
}
}
2024-10-05 12:48:09 +02:00
// WithNoDefaultUserAgent disables the inclusion of a default User-Agent header in the Msg during its creation or
// initialization.
2024-02-23 23:41:58 +01:00
func WithNoDefaultUserAgent ( ) MsgOption {
return func ( m * Msg ) {
m . noDefaultUserAgent = true
2023-01-31 18:35:48 +01:00
}
}
2024-10-05 13:39:21 +02:00
// SetCharset sets or overrides the currently set encoding charset of the Msg.
2022-03-11 10:29:44 +01:00
func ( m * Msg ) SetCharset ( c Charset ) {
m . charset = c
}
2024-10-05 13:39:21 +02:00
// SetEncoding sets or overrides the currently set Encoding of the Msg.
2022-03-11 10:29:44 +01:00
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
}
2024-10-05 13:39:21 +02:00
// SetBoundary sets or overrides the currently set boundary of the Msg.
//
// Note that by default we create random MIME boundaries. This should only be used if a specific boundary is
// required.
2022-03-13 17:15:23 +01:00
func ( m * Msg ) SetBoundary ( b string ) {
m . boundary = b
}
2024-10-05 13:39:21 +02:00
// SetMIMEVersion sets or overrides the currently set MIME version of the Msg.
//
// Note that in the context of email, MIME Version 1.0 is the only officially standardized and supported
// version. While MIME has been updated and extended over time (via various RFCs), these updates and extensions
// do not introduce new MIME versions; they refine or add features within the framework of MIME 1.0.
// Therefore there should be no reason to ever use this MsgOption.
//
// https://datatracker.ietf.org/doc/html/rfc1521
//
// https://datatracker.ietf.org/doc/html/rfc2045
//
// https://datatracker.ietf.org/doc/html/rfc2049
2022-03-13 17:15:23 +01:00
func ( m * Msg ) SetMIMEVersion ( mv MIMEVersion ) {
m . mimever = mv
}
2024-10-05 13:39:21 +02:00
// SetPGPType sets or overrides the currently set PGP type for the Msg, determining the encryption or
// signature method.
2023-01-31 18:35:48 +01:00
func ( m * Msg ) SetPGPType ( t PGPType ) {
m . pgptype = t
}
2024-10-05 13:39:21 +02:00
// Encoding returns the currently set Encoding of the Msg as string.
2022-03-13 10:49:07 +01:00
func ( m * Msg ) Encoding ( ) string {
return m . encoding . String ( )
}
2024-10-05 13:39:21 +02:00
// Charset returns the currently set Charset of the Msg as string.
2022-03-13 10:49:07 +01:00
func ( m * Msg ) Charset ( ) string {
return m . charset . String ( )
}
2024-10-05 13:39:21 +02:00
// SetHeader sets a generic header field of the Msg.
2022-11-19 11:22:20 +01:00
//
2024-10-05 13:39:21 +02:00
// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead.
// For adding address headers like "To:" or "From", use SetAddrHeader instead.
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetHeader ( header Header , values ... string ) {
m . SetGenHeader ( header , values ... )
2022-11-19 11:22:20 +01:00
}
2024-10-05 13:39:21 +02:00
// SetGenHeader sets a generic header field of the Msg to the provided list of values.
//
// Note: for adding email address related headers (like "To:" or "From") use SetAddrHeader instead.
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetGenHeader ( header Header , values ... string ) {
2022-10-20 18:06:26 +02:00
if m . genHeader == nil {
m . genHeader = make ( map [ Header ] [ ] string )
}
2024-02-24 18:26:30 +01:00
for i , val := range values {
values [ i ] = m . encodeString ( val )
2022-03-11 10:29:44 +01:00
}
2024-02-24 18:26:30 +01:00
m . genHeader [ header ] = values
2022-03-10 16:19:51 +01:00
}
2024-10-05 13:39:21 +02:00
// SetHeaderPreformatted sets a generic header field of the Msg, which content is already preformatted.
2022-10-26 15:33:03 +02:00
//
2024-10-05 13:39:21 +02:00
// Deprecated: This method only exists for compatibility reason. Please use SetGenHeaderPreformatted instead.
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetHeaderPreformatted ( header Header , value string ) {
m . SetGenHeaderPreformatted ( header , value )
2022-11-19 11:22:20 +01:00
}
2024-10-05 13:39:21 +02:00
// SetGenHeaderPreformatted sets a generic header field of the Msg which content is already preformated.
//
// This method does not take a slice of values but only a single value. The reason for this is that we do not
// perform any content alteration on these kind of headers and expect the user to have already taken care of
// any kind of formatting required for the header.
2022-11-19 11:22:20 +01:00
//
2024-10-05 13:39:21 +02:00
// Note: This method should be used only as a last resort. Since the user is respondible for the formatting of
// the message header, we cannot guarantee any compliance with the RFC 2822. It is advised to use SetGenHeader
// instead.
2022-10-26 15:33:03 +02:00
//
2024-10-05 13:39:21 +02:00
// https://datatracker.ietf.org/doc/html/rfc2822
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetGenHeaderPreformatted ( header Header , value string ) {
2022-10-26 15:33:03 +02:00
if m . preformHeader == nil {
m . preformHeader = make ( map [ Header ] string )
}
2024-02-24 18:26:30 +01:00
m . preformHeader [ header ] = value
2022-10-26 15:33:03 +02:00
}
2024-10-05 13:56:47 +02:00
// SetAddrHeader sets the specified AddrHeader for the Msg to the given values.
//
// Addresses are parsed according to RFC 5322. If parsing of ANY of the provided values fails,
// and error is returned. If you cannot guarantee that all provided values are valid, you can
// use SetAddrHeaderIgnoreInvalid instead, which will skip any parsing error silently.
//
// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetAddrHeader ( header AddrHeader , values ... string ) error {
2022-10-20 18:06:26 +02:00
if m . addrHeader == nil {
m . addrHeader = make ( map [ AddrHeader ] [ ] * mail . Address )
}
2024-02-24 18:26:30 +01:00
var addresses [ ] * mail . Address
for _ , addrVal := range values {
address , err := mail . ParseAddress ( addrVal )
2022-03-10 16:19:51 +01:00
if err != nil {
2024-02-24 18:26:30 +01:00
return fmt . Errorf ( errParseMailAddr , addrVal , err )
2022-03-10 16:19:51 +01:00
}
2024-02-24 18:26:30 +01:00
addresses = append ( addresses , address )
2022-03-10 16:19:51 +01:00
}
2024-02-24 18:26:30 +01:00
switch header {
2022-03-09 16:52:23 +01:00
case HeaderFrom :
2024-02-24 18:26:30 +01:00
if len ( addresses ) > 0 {
m . addrHeader [ header ] = [ ] * mail . Address { addresses [ 0 ] }
2023-11-20 18:36:19 +01:00
}
2022-03-09 16:52:23 +01:00
default :
2024-02-24 18:26:30 +01:00
m . addrHeader [ header ] = addresses
2022-03-09 16:52:23 +01:00
}
2022-03-10 16:19:51 +01:00
return nil
}
2024-10-05 13:56:47 +02:00
// SetAddrHeaderIgnoreInvalid sets the specified AddrHeader for the Msg to the given values.
//
// Addresses are parsed according to RFC 5322. If parsing of ANY of the provided values fails,
// the error is ignored and the address omiitted from the address list.
//
// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetAddrHeaderIgnoreInvalid ( header AddrHeader , values ... string ) {
var addresses [ ] * mail . Address
for _ , addrVal := range values {
address , err := mail . ParseAddress ( m . encodeString ( addrVal ) )
2022-03-10 16:19:51 +01:00
if err != nil {
continue
}
2024-02-24 18:26:30 +01:00
addresses = append ( addresses , address )
2022-03-10 16:19:51 +01:00
}
2024-02-24 18:26:30 +01:00
m . addrHeader [ header ] = addresses
2022-03-10 16:19:51 +01:00
}
2024-10-05 13:56:47 +02:00
// EnvelopeFrom sets the envelope from address for the Msg.
//
// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the Client for the
// communication with the SMTP server. If the Msg has no "FROM" address set in the mail body, the msgWriter will
// try to use the envelope from address, if this has been set for the Msg. The provided address is validated
// according to RFC 5322 and will return an error if the validation fails.
//
// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4
2024-02-24 18:26:30 +01:00
func ( m * Msg ) EnvelopeFrom ( from string ) error {
return m . SetAddrHeader ( HeaderEnvelopeFrom , from )
2022-06-13 10:18:35 +02:00
}
// EnvelopeFromFormat takes a name and address, formats them RFC5322 compliant and stores them as
// the envelope FROM address header field
2024-02-24 18:26:30 +01:00
func ( m * Msg ) EnvelopeFromFormat ( name , addr string ) error {
return m . SetAddrHeader ( HeaderEnvelopeFrom , fmt . Sprintf ( ` "%s" <%s> ` , name , addr ) )
2022-06-13 10:18:35 +02:00
}
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) From ( from string ) error {
return m . SetAddrHeader ( HeaderFrom , from )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) FromFormat ( name , addr string ) error {
return m . SetAddrHeader ( HeaderFrom , fmt . Sprintf ( ` "%s" <%s> ` , name , addr ) )
2022-03-12 20:05:43 +01:00
}
2022-03-10 16:19:51 +01:00
// To takes and validates a given mail address list sets the To: addresses of the Msg
2024-02-24 18:26:30 +01:00
func ( m * Msg ) To ( rcpts ... string ) error {
return m . SetAddrHeader ( HeaderTo , rcpts ... )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) AddTo ( rcpt string ) error {
return m . addAddr ( HeaderTo , rcpt )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) AddToFormat ( name , addr string ) error {
return m . addAddr ( HeaderTo , fmt . Sprintf ( ` "%s" <%s> ` , name , addr ) )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) ToIgnoreInvalid ( rcpts ... string ) {
m . SetAddrHeaderIgnoreInvalid ( HeaderTo , rcpts ... )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) ToFromString ( rcpts string ) error {
return m . To ( strings . Split ( rcpts , "," ) ... )
2023-11-10 18:07:56 +01:00
}
2022-03-10 16:19:51 +01:00
// Cc takes and validates a given mail address list sets the Cc: addresses of the Msg
2024-02-24 18:26:30 +01:00
func ( m * Msg ) Cc ( rcpts ... string ) error {
return m . SetAddrHeader ( HeaderCc , rcpts ... )
2022-03-10 16:19:51 +01:00
}
2022-03-13 10:49:07 +01:00
// AddCc adds an additional address to the Cc address header field
2024-02-24 18:26:30 +01:00
func ( m * Msg ) AddCc ( rcpt string ) error {
return m . addAddr ( HeaderCc , rcpt )
2022-03-13 10:49:07 +01:00
}
// AddCcFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional Cc address header field
2024-02-24 18:26:30 +01:00
func ( m * Msg ) AddCcFormat ( name , addr string ) error {
return m . addAddr ( HeaderCc , fmt . Sprintf ( ` "%s" <%s> ` , name , addr ) )
2022-03-13 10:49:07 +01:00
}
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) CcIgnoreInvalid ( rcpts ... string ) {
m . SetAddrHeaderIgnoreInvalid ( HeaderCc , rcpts ... )
2022-03-10 16:19:51 +01:00
}
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) CcFromString ( rcpts string ) error {
return m . Cc ( strings . Split ( rcpts , "," ) ... )
2023-11-10 18:07:56 +01:00
}
2022-03-10 16:19:51 +01:00
// Bcc takes and validates a given mail address list sets the Bcc: addresses of the Msg
2024-02-24 18:26:30 +01:00
func ( m * Msg ) Bcc ( rcpts ... string ) error {
return m . SetAddrHeader ( HeaderBcc , rcpts ... )
2022-03-10 16:19:51 +01:00
}
2022-03-13 10:49:07 +01:00
// AddBcc adds an additional address to the Bcc address header field
2024-02-24 18:26:30 +01:00
func ( m * Msg ) AddBcc ( rcpt string ) error {
return m . addAddr ( HeaderBcc , rcpt )
2022-03-13 10:49:07 +01:00
}
// AddBccFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional Bcc address header field
2024-02-24 18:26:30 +01:00
func ( m * Msg ) AddBccFormat ( name , addr string ) error {
return m . addAddr ( HeaderBcc , fmt . Sprintf ( ` "%s" <%s> ` , name , addr ) )
2022-03-13 10:49:07 +01:00
}
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) BccIgnoreInvalid ( rcpts ... string ) {
m . SetAddrHeaderIgnoreInvalid ( HeaderBcc , rcpts ... )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) BccFromString ( rcpts string ) error {
return m . Bcc ( strings . Split ( rcpts , "," ) ... )
2023-11-10 18:07:56 +01:00
}
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) ReplyTo ( addr string ) error {
replyTo , err := mail . ParseAddress ( addr )
2022-03-13 19:36:24 +01:00
if err != nil {
return fmt . Errorf ( "failed to parse reply-to address: %w" , err )
}
2024-02-24 18:26:30 +01:00
m . SetGenHeader ( HeaderReplyTo , replyTo . 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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) ReplyToFormat ( name , addr string ) error {
return m . ReplyTo ( fmt . Sprintf ( ` "%s" <%s> ` , name , addr ) )
2022-03-13 19:36:24 +01:00
}
2022-03-13 10:49:07 +01:00
// addAddr adds an additional address to the given addrHeader of the Msg
2024-02-24 18:26:30 +01:00
func ( m * Msg ) addAddr ( header AddrHeader , addr string ) error {
var addresses [ ] string
for _ , address := range m . addrHeader [ header ] {
addresses = append ( addresses , address . String ( ) )
2022-03-13 10:49:07 +01:00
}
2024-02-24 18:26:30 +01:00
addresses = append ( addresses , addr )
return m . SetAddrHeader ( header , addresses ... )
2022-03-13 10:49:07 +01:00
}
// Subject sets the "Subject" header field of the Msg
2024-02-24 18:26:30 +01:00
func ( m * Msg ) Subject ( subj string ) {
m . SetGenHeader ( HeaderSubject , subj )
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 ( ) {
2024-02-24 18:26:30 +01:00
hostname , err := os . Hostname ( )
2022-03-09 16:52:23 +01:00
if err != nil {
2024-02-24 18:26:30 +01:00
hostname = "localhost.localdomain"
2022-03-09 16:52:23 +01:00
}
2024-09-20 20:39:50 +02:00
randNumPrimary := randNum ( 100000000 )
randNumSecondary := randNum ( 10000 )
2024-02-24 18:26:30 +01:00
randString , _ := randomStringSecure ( 17 )
procID := os . Getpid ( ) * randNumSecondary
messageID := fmt . Sprintf ( "%d.%d%d.%s@%s" , procID , randNumPrimary , randNumSecondary ,
randString , hostname )
m . SetMessageIDWithValue ( messageID )
2022-03-09 16:52:23 +01:00
}
2024-09-19 11:46:53 +02:00
// GetMessageID returns the message ID of the Msg as string value. If no message ID
// is set, an empty string will be returned
func ( m * Msg ) GetMessageID ( ) string {
if msgidheader , ok := m . genHeader [ HeaderMessageID ] ; ok {
if len ( msgidheader ) > 0 {
return msgidheader [ 0 ]
}
}
return ""
}
2022-03-09 16:52:23 +01:00
// SetMessageIDWithValue sets the message id for the mail
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetMessageIDWithValue ( messageID string ) {
m . SetGenHeader ( HeaderMessageID , fmt . Sprintf ( "<%s>" , messageID ) )
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 ( ) {
2024-02-24 18:26:30 +01:00
now := time . Now ( ) . Format ( time . RFC1123Z )
m . SetGenHeader ( HeaderDate , now )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetDateWithValue ( timeVal time . Time ) {
m . SetGenHeader ( HeaderDate , timeVal . 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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetImportance ( importance Importance ) {
if importance == ImportanceNormal {
2022-03-13 19:24:46 +01:00
return
}
2024-02-24 18:26:30 +01:00
m . SetGenHeader ( HeaderImportance , importance . String ( ) )
m . SetGenHeader ( HeaderPriority , importance . NumString ( ) )
m . SetGenHeader ( HeaderXPriority , importance . XPrioString ( ) )
m . SetGenHeader ( HeaderXMSMailPriority , importance . 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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetOrganization ( org string ) {
m . SetGenHeader ( HeaderOrganization , org )
2022-03-14 16:25:24 +01:00
}
// SetUserAgent sets the User-Agent/X-Mailer header for the Msg
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetUserAgent ( userAgent string ) {
m . SetGenHeader ( HeaderUserAgent , userAgent )
m . SetGenHeader ( HeaderXMailer , userAgent )
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) RequestMDNTo ( rcpts ... string ) error {
var addresses [ ] string
for _ , addrVal := range rcpts {
address , err := mail . ParseAddress ( addrVal )
2022-09-09 11:35:45 +02:00
if err != nil {
2024-02-24 18:26:30 +01:00
return fmt . Errorf ( errParseMailAddr , addrVal , err )
2022-09-09 11:35:45 +02:00
}
2024-02-24 18:26:30 +01:00
addresses = append ( addresses , address . String ( ) )
2022-09-09 11:35:45 +02:00
}
2023-11-29 17:26:58 +01:00
if _ , ok := m . genHeader [ HeaderDispositionNotificationTo ] ; ok {
2024-02-24 18:26:30 +01:00
m . genHeader [ HeaderDispositionNotificationTo ] = addresses
2023-11-29 17:26:58 +01:00
}
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) RequestMDNToFormat ( name , addr string ) error {
return m . RequestMDNTo ( fmt . Sprintf ( ` %s <%s> ` , name , addr ) )
2022-09-09 11:35:45 +02:00
}
// RequestMDNAddTo adds an additional recipient to the recipient list of the MDN
2024-02-24 18:26:30 +01:00
func ( m * Msg ) RequestMDNAddTo ( rcpt string ) error {
address , err := mail . ParseAddress ( rcpt )
2022-09-09 11:35:45 +02:00
if err != nil {
2024-02-24 18:26:30 +01:00
return fmt . Errorf ( errParseMailAddr , rcpt , err )
2022-09-09 11:35:45 +02:00
}
2024-02-24 18:26:30 +01:00
var addresses [ ] string
addresses = append ( addresses , m . genHeader [ HeaderDispositionNotificationTo ] ... )
addresses = append ( addresses , address . String ( ) )
2023-11-29 17:26:58 +01:00
if _ , ok := m . genHeader [ HeaderDispositionNotificationTo ] ; ok {
2024-02-24 18:26:30 +01:00
m . genHeader [ HeaderDispositionNotificationTo ] = addresses
2023-11-29 17:26:58 +01:00
}
2022-09-09 11:35:45 +02:00
return nil
}
// RequestMDNAddToFormat adds an additional formated recipient to the recipient list of the MDN
2024-02-24 18:26:30 +01:00
func ( m * Msg ) RequestMDNAddToFormat ( name , addr string ) error {
return m . RequestMDNAddTo ( fmt . Sprintf ( ` "%s" <%s> ` , name , addr ) )
2022-09-09 11:35:45 +02:00
}
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
2024-02-24 18:26:30 +01:00
// the first mail body FROM address. If useFullAddr is true, it will return the full address string
// including the address name, if set
func ( m * Msg ) GetSender ( useFullAddr bool ) ( string , error ) {
from , ok := m . addrHeader [ HeaderEnvelopeFrom ]
if ! ok || len ( from ) == 0 {
from , ok = m . addrHeader [ HeaderFrom ]
if ! ok || len ( from ) == 0 {
2022-06-13 10:18:35 +02:00
return "" , ErrNoFromAddress
}
2022-03-11 16:57:14 +01:00
}
2024-02-24 18:26:30 +01:00
if useFullAddr {
return from [ 0 ] . String ( ) , nil
2022-03-11 16:57:14 +01:00
}
2024-02-24 18:26:30 +01:00
return from [ 0 ] . Address , nil
2022-03-12 15:10:01 +01:00
}
// GetRecipients returns a list of the currently set TO/CC/BCC addresses.
func ( m * Msg ) GetRecipients ( ) ( [ ] string , error ) {
2024-02-24 18:26:30 +01:00
var rcpts [ ] string
for _ , addressType := range [ ] AddrHeader { HeaderTo , HeaderCc , HeaderBcc } {
addresses , ok := m . addrHeader [ addressType ]
if ! ok || len ( addresses ) == 0 {
2022-03-12 15:10:01 +01:00
continue
}
2024-02-24 18:26:30 +01:00
for _ , r := range addresses {
rcpts = append ( rcpts , r . Address )
2022-03-12 15:10:01 +01:00
}
}
2024-02-24 18:26:30 +01:00
if len ( rcpts ) <= 0 {
return rcpts , ErrNoRcptAddresses
2022-03-12 15:10:01 +01:00
}
2024-02-24 18:26:30 +01:00
return rcpts , nil
2022-03-12 15:10:01 +01:00
}
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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) GetAddrHeader ( header AddrHeader ) [ ] * mail . Address {
return m . addrHeader [ header ]
2022-11-19 11:22:20 +01:00
}
// GetAddrHeaderString returns the address string of the requested address header of the Msg
2024-02-24 18:26:30 +01:00
func ( m * Msg ) GetAddrHeaderString ( header AddrHeader ) [ ] string {
var addresses [ ] string
for _ , mh := range m . addrHeader [ header ] {
addresses = append ( addresses , mh . String ( ) )
2022-11-19 11:22:20 +01:00
}
2024-02-24 18:26:30 +01:00
return addresses
2022-11-19 11:22:20 +01:00
}
// 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
2024-02-24 18:26:30 +01:00
func ( m * Msg ) GetGenHeader ( header Header ) [ ] string {
return m . genHeader [ header ]
2022-10-02 12:25:49 +02:00
}
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
}
2024-07-02 10:58:13 +02:00
// GetBoundary returns the boundary of the Msg
func ( m * Msg ) GetBoundary ( ) string {
return m . boundary
}
2024-08-14 14:55:08 +02:00
// SetAttachments sets the attachments of the message.
func ( m * Msg ) SetAttachments ( files [ ] * File ) {
2024-02-24 18:26:30 +01:00
m . attachments = files
2022-10-12 13:36:52 +02:00
}
2024-08-14 14:55:08 +02:00
// SetAttachements sets the attachments of the message.
//
// Deprecated: use SetAttachments instead.
func ( m * Msg ) SetAttachements ( files [ ] * File ) {
m . SetAttachments ( files )
}
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.
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetEmbeds ( files [ ] * File ) {
m . embeds = files
2023-01-29 13:48:51 +01:00
}
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.
2024-02-24 18:26:30 +01:00
func ( m * Msg ) SetBodyString ( contentType ContentType , content string , opts ... PartOption ) {
buffer := bytes . NewBufferString ( content )
writeFunc := writeFuncFromBuffer ( buffer )
m . SetBodyWriter ( contentType , writeFunc , opts ... )
2022-03-13 17:15:23 +01:00
}
// SetBodyWriter sets the body of the message.
2024-02-24 21:26:55 +01:00
func ( m * Msg ) SetBodyWriter (
contentType ContentType , writeFunc func ( io . Writer ) ( int64 , error ) ,
opts ... PartOption ,
) {
2024-02-24 18:26:30 +01:00
p := m . newPart ( contentType , opts ... )
2024-02-27 11:21:28 +01:00
p . writeFunc = writeFunc
2022-03-13 17:15:23 +01:00
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) SetBodyHTMLTemplate ( tpl * ht . Template , data interface { } , opts ... PartOption ) error {
if tpl == nil {
2024-08-16 10:16:53 +02:00
return errors . New ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2024-02-24 21:26:55 +01:00
buffer := bytes . Buffer { }
if err := tpl . Execute ( & buffer , data ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
2024-02-24 21:26:55 +01:00
writeFunc := writeFuncFromBuffer ( & buffer )
m . SetBodyWriter ( TypeTextHTML , writeFunc , opts ... )
2022-06-03 10:40:54 +02:00
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) SetBodyTextTemplate ( tpl * tt . Template , data interface { } , opts ... PartOption ) error {
if tpl == nil {
2024-08-16 10:16:53 +02:00
return errors . New ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2022-06-03 10:40:54 +02:00
buf := bytes . Buffer { }
2024-02-24 21:26:55 +01:00
if err := tpl . Execute ( & buf , data ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
2024-02-24 21:26:55 +01:00
writeFunc := writeFuncFromBuffer ( & buf )
m . SetBodyWriter ( TypeTextPlain , writeFunc , opts ... )
2022-06-03 10:40:54 +02:00
return nil
}
2022-03-13 17:15:23 +01:00
// AddAlternativeString sets the alternative body of the message.
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AddAlternativeString ( contentType ContentType , content string , opts ... PartOption ) {
buffer := bytes . NewBufferString ( content )
writeFunc := writeFuncFromBuffer ( buffer )
m . AddAlternativeWriter ( contentType , writeFunc , opts ... )
2022-03-13 17:15:23 +01:00
}
// AddAlternativeWriter sets the body of the message.
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AddAlternativeWriter (
contentType ContentType , writeFunc func ( io . Writer ) ( int64 , error ) ,
opts ... PartOption ,
) {
part := m . newPart ( contentType , opts ... )
2024-02-27 11:21:28 +01:00
part . writeFunc = writeFunc
2024-02-24 21:26:55 +01:00
m . parts = append ( m . parts , part )
2022-03-13 17:15:23 +01:00
}
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AddAlternativeHTMLTemplate ( tpl * ht . Template , data interface { } , opts ... PartOption ) error {
if tpl == nil {
2024-08-16 10:16:53 +02:00
return errors . New ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2024-02-24 21:26:55 +01:00
buffer := bytes . Buffer { }
if err := tpl . Execute ( & buffer , data ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
2024-02-24 21:26:55 +01:00
writeFunc := writeFuncFromBuffer ( & buffer )
m . AddAlternativeWriter ( TypeTextHTML , writeFunc , opts ... )
2022-06-03 10:40:54 +02:00
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AddAlternativeTextTemplate ( tpl * tt . Template , data interface { } , opts ... PartOption ) error {
if tpl == nil {
2024-08-16 10:16:53 +02:00
return errors . New ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2024-02-24 21:26:55 +01:00
buffer := bytes . Buffer { }
if err := tpl . Execute ( & buffer , data ) ; err != nil {
2022-06-03 12:27:26 +02:00
return fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
2024-02-24 21:26:55 +01:00
writeFunc := writeFuncFromBuffer ( & buffer )
m . AddAlternativeWriter ( TypeTextPlain , writeFunc , opts ... )
2022-06-03 10:40:54 +02:00
return nil
}
2022-03-14 10:29:53 +01:00
// AttachFile adds an attachment File to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AttachFile ( name string , opts ... FileOption ) {
file := fileFromFS ( name )
if file == nil {
2022-03-15 11:56:21 +01:00
return
}
2024-02-24 21:26:55 +01:00
m . attachments = m . appendFile ( m . attachments , file , opts ... )
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AttachReader ( name string , reader io . Reader , opts ... FileOption ) error {
file , err := fileFromReader ( name , reader )
2023-12-24 17:04:55 +01:00
if err != nil {
return err
}
2024-02-24 21:26:55 +01:00
m . attachments = m . appendFile ( m . attachments , file , opts ... )
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AttachReadSeeker ( name string , reader io . ReadSeeker , opts ... FileOption ) {
file := fileFromReadSeeker ( name , reader )
m . attachments = m . appendFile ( m . attachments , file , opts ... )
2023-01-31 17:38:31 +01:00
}
2022-06-03 10:40:54 +02:00
// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AttachHTMLTemplate (
name string , tpl * ht . Template , data interface { } , opts ... FileOption ,
) error {
file , err := fileFromHTMLTemplate ( name , tpl , data )
2022-06-03 10:40:54 +02:00
if err != nil {
return fmt . Errorf ( "failed to attach template: %w" , err )
}
2024-02-24 21:26:55 +01:00
m . attachments = m . appendFile ( m . attachments , file , opts ... )
2022-06-03 10:40:54 +02:00
return nil
}
// AttachTextTemplate adds the output of a text/template.Template pointer as File attachment to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AttachTextTemplate (
name string , tpl * tt . Template , data interface { } , opts ... FileOption ,
) error {
file , err := fileFromTextTemplate ( name , tpl , data )
2022-06-01 16:49:34 +02:00
if err != nil {
return fmt . Errorf ( "failed to attach template: %w" , err )
}
2024-02-24 21:26:55 +01:00
m . attachments = m . appendFile ( m . attachments , file , opts ... )
2022-06-01 16:49:34 +02:00
return nil
}
2022-07-07 10:46:57 +02:00
// AttachFromEmbedFS adds an attachment File from an embed.FS to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) AttachFromEmbedFS ( name string , fs * embed . FS , opts ... FileOption ) error {
if fs == nil {
2022-07-07 10:46:57 +02:00
return fmt . Errorf ( "embed.FS must not be nil" )
}
2024-02-24 21:26:55 +01:00
file , err := fileFromEmbedFS ( name , fs )
2022-07-07 10:46:57 +02:00
if err != nil {
return err
}
2024-02-24 21:26:55 +01:00
m . attachments = m . appendFile ( m . attachments , file , opts ... )
2022-07-07 10:46:57 +02:00
return nil
}
2022-03-14 10:29:53 +01:00
// EmbedFile adds an embedded File to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) EmbedFile ( name string , opts ... FileOption ) {
file := fileFromFS ( name )
if file == nil {
2022-03-15 11:56:21 +01:00
return
}
2024-02-24 21:26:55 +01:00
m . embeds = m . appendFile ( m . embeds , file , opts ... )
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) EmbedReader ( name string , reader io . Reader , opts ... FileOption ) error {
file , err := fileFromReader ( name , reader )
2023-12-24 17:04:55 +01:00
if err != nil {
return err
}
2024-02-24 21:26:55 +01:00
m . embeds = m . appendFile ( m . embeds , file , opts ... )
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) EmbedReadSeeker ( name string , reader io . ReadSeeker , opts ... FileOption ) {
file := fileFromReadSeeker ( name , reader )
m . embeds = m . appendFile ( m . embeds , file , opts ... )
2023-01-31 17:38:31 +01:00
}
2022-06-03 10:40:54 +02:00
// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) EmbedHTMLTemplate (
name string , tpl * ht . Template , data interface { } , opts ... FileOption ,
) error {
file , err := fileFromHTMLTemplate ( name , tpl , data )
2022-06-03 10:40:54 +02:00
if err != nil {
return fmt . Errorf ( "failed to embed template: %w" , err )
}
2024-02-24 21:26:55 +01:00
m . embeds = m . appendFile ( m . embeds , file , opts ... )
2022-06-03 10:40:54 +02:00
return nil
}
// EmbedTextTemplate adds the output of a text/template.Template pointer as embedded File to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) EmbedTextTemplate (
name string , tpl * tt . Template , data interface { } , opts ... FileOption ,
) error {
file , err := fileFromTextTemplate ( name , tpl , data )
2022-06-01 16:49:34 +02:00
if err != nil {
return fmt . Errorf ( "failed to embed template: %w" , err )
}
2024-02-24 21:26:55 +01:00
m . embeds = m . appendFile ( m . embeds , file , opts ... )
2022-06-01 16:49:34 +02:00
return nil
}
2022-07-07 10:46:57 +02:00
// EmbedFromEmbedFS adds an embedded File from an embed.FS to the Msg
2024-02-24 21:26:55 +01:00
func ( m * Msg ) EmbedFromEmbedFS ( name string , fs * embed . FS , opts ... FileOption ) error {
if fs == nil {
2022-07-07 10:46:57 +02:00
return fmt . Errorf ( "embed.FS must not be nil" )
}
2024-02-24 21:26:55 +01:00
file , err := fileFromEmbedFS ( name , fs )
2022-07-07 10:46:57 +02:00
if err != nil {
return err
}
2024-02-24 21:26:55 +01:00
m . embeds = m . appendFile ( m . embeds , file , opts ... )
2022-07-07 10:46:57 +02:00
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) applyMiddlewares ( msg * Msg ) * Msg {
for _ , middleware := range m . middlewares {
msg = middleware . Handle ( msg )
2022-09-22 18:05:47 +02:00
}
2024-02-24 21:26:55 +01:00
return msg
2022-09-22 18:05:47 +02:00
}
2022-05-24 15:46:59 +02:00
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
2024-02-24 21:26:55 +01:00
func ( m * Msg ) WriteTo ( writer io . Writer ) ( int64 , error ) {
mw := & msgWriter { writer : writer , charset : m . charset , encoder : m . encoder }
2022-09-22 18:05:47 +02:00
mw . writeMsg ( m . applyMiddlewares ( m ) )
2024-02-24 12:43:01 +01:00
return mw . bytesWritten , 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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) WriteToSkipMiddleware ( writer io . Writer , middleWareType MiddlewareType ) ( int64 , error ) {
var origMiddlewares , middlewares [ ] Middleware
origMiddlewares = m . middlewares
2022-10-25 16:42:18 +02:00
for i := range m . middlewares {
2024-02-24 21:26:55 +01:00
if m . middlewares [ i ] . Type ( ) == middleWareType {
2022-10-25 16:42:18 +02:00
continue
}
2024-02-24 21:26:55 +01:00
middlewares = append ( middlewares , m . middlewares [ i ] )
2022-10-25 16:42:18 +02:00
}
2024-02-24 21:26:55 +01:00
m . middlewares = middlewares
mw := & msgWriter { writer : writer , charset : m . charset , encoder : m . encoder }
2022-10-25 16:42:18 +02:00
mw . writeMsg ( m . applyMiddlewares ( m ) )
2024-02-24 21:26:55 +01:00
m . middlewares = origMiddlewares
2024-02-24 12:43:01 +01:00
return mw . bytesWritten , mw . err
2022-10-25 16:42:18 +02:00
}
2022-06-02 16:22:48 +02:00
// Write is an alias method to WriteTo due to compatibility reasons
2024-02-24 21:26:55 +01:00
func ( m * Msg ) Write ( writer io . Writer ) ( int64 , error ) {
return m . WriteTo ( writer )
2022-05-24 15:46:59 +02:00
}
2022-03-14 10:29:53 +01:00
// appendFile adds a File to the Msg (as attachment or embed)
2024-02-24 21:26:55 +01:00
func ( m * Msg ) appendFile ( files [ ] * File , file * File , opts ... FileOption ) [ ] * File {
2022-03-14 10:29:53 +01:00
// Override defaults with optionally provided FileOption functions
2024-02-24 21:26:55 +01:00
for _ , opt := range opts {
if opt == nil {
2022-03-14 10:29:53 +01:00
continue
}
2024-02-24 21:26:55 +01:00
opt ( file )
2022-03-14 10:29:53 +01:00
}
2024-02-24 21:26:55 +01:00
if files == nil {
return [ ] * File { file }
2022-03-14 10:29:53 +01:00
}
2024-02-24 21:26:55 +01:00
return append ( files , file )
2022-03-14 10:29:53 +01:00
}
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) WriteToFile ( name string ) error {
file , err := os . Create ( name )
2022-06-06 16:43:04 +02:00
if err != nil {
return fmt . Errorf ( "failed to create output file: %w" , err )
}
2024-02-24 21:26:55 +01:00
defer func ( ) { _ = file . Close ( ) } ( )
_ , err = m . WriteTo ( file )
2022-06-06 16:43:04 +02:00
if err != nil {
return fmt . Errorf ( "failed to write to output file: %w" , err )
}
2024-02-24 21:26:55 +01:00
return file . Close ( )
2022-06-06 16:43:04 +02:00
}
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) WriteToSendmailWithCommand ( sendmailPath string ) error {
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Second * 5 )
defer cancel ( )
return m . WriteToSendmailWithContext ( ctx , sendmailPath )
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) WriteToSendmailWithContext ( ctx context . Context , sendmailPath string , args ... string ) error {
cmdCtx := exec . CommandContext ( ctx , sendmailPath )
cmdCtx . Args = append ( cmdCtx . Args , "-oi" , "-t" )
cmdCtx . Args = append ( cmdCtx . Args , args ... )
2022-03-14 15:26:53 +01:00
2024-02-24 21:26:55 +01:00
stdErr , err := cmdCtx . StderrPipe ( )
2022-03-18 22:23:03 +01:00
if err != nil {
return fmt . Errorf ( "failed to set STDERR pipe: %w" , err )
}
2022-03-14 15:26:53 +01:00
2024-02-24 21:26:55 +01:00
stdIn , err := cmdCtx . StdinPipe ( )
2022-03-14 15:26:53 +01:00
if err != nil {
return fmt . Errorf ( "failed to set STDIN pipe: %w" , err )
}
2024-02-24 21:26:55 +01:00
if stdErr == nil || stdIn == nil {
2023-11-29 16:43:50 +01:00
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
2024-02-24 21:26:55 +01:00
if err = cmdCtx . Start ( ) ; err != nil {
2022-03-14 15:26:53 +01:00
return fmt . Errorf ( "could not start sendmail execution: %w" , err )
}
2024-02-24 21:26:55 +01:00
_ , err = m . WriteTo ( stdIn )
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
2024-02-24 21:26:55 +01:00
if err = stdIn . 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
2024-02-24 21:26:55 +01:00
sendmailErr , err := io . ReadAll ( stdErr )
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 )
}
2024-02-24 21:26:55 +01:00
if len ( sendmailErr ) > 0 {
return fmt . Errorf ( "sendmail command failed: %s" , string ( sendmailErr ) )
2022-03-14 15:26:53 +01:00
}
2024-02-24 21:26:55 +01:00
if err = cmdCtx . 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 {
2024-02-24 21:26:55 +01:00
reader := & Reader { }
buffer := bytes . Buffer { }
_ , err := m . Write ( & buffer )
2022-10-20 18:03:57 +02:00
if err != nil {
2024-02-24 21:26:55 +01:00
reader . err = fmt . Errorf ( "failed to write Msg to Reader buffer: %w" , err )
2022-10-20 18:03:57 +02:00
}
2024-02-26 00:56:29 +01:00
reader . buffer = buffer . Bytes ( )
2024-02-24 21:26:55 +01:00
return reader
2022-10-20 15:52:53 +02:00
}
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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) UpdateReader ( reader * Reader ) {
buffer := bytes . Buffer { }
_ , err := m . Write ( & buffer )
reader . Reset ( )
2024-02-26 00:56:29 +01:00
reader . buffer = buffer . Bytes ( )
2024-02-24 21:26:55 +01:00
reader . 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 {
2024-02-24 21:26:55 +01:00
var err * SendError
if errors . As ( m . sendError , & err ) && err != nil {
return err . 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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) encodeString ( str string ) string {
return m . encoder . Encode ( string ( m . charset ) , str )
2022-03-14 10:29:53 +01:00
}
// hasAlt returns true if the Msg has more than one part
func ( m * Msg ) hasAlt ( ) bool {
2024-02-24 21:26:55 +01:00
count := 0
for _ , part := range m . parts {
2024-02-27 11:21:28 +01:00
if ! part . isDeleted {
2024-02-24 21:26:55 +01:00
count ++
2023-01-28 14:39:14 +01:00
}
}
2024-02-24 21:26:55 +01:00
return count > 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
2024-02-24 21:26:55 +01:00
func ( m * Msg ) newPart ( contentType ContentType , opts ... PartOption ) * Part {
2022-03-13 17:15:23 +01:00
p := & Part {
2024-02-27 11:21:28 +01:00
contentType : contentType ,
charset : m . charset ,
encoding : m . encoding ,
2022-03-13 17:15:23 +01:00
}
// Override defaults with optionally provided MsgOption functions
2024-02-24 21:26:55 +01:00
for _ , opt := range opts {
if opt == nil {
2022-03-13 17:15:23 +01:00
continue
}
2024-02-24 21:26:55 +01:00
opt ( p )
2022-03-13 17:15:23 +01:00
}
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 ( ) {
2024-02-23 23:41:58 +01:00
if m . noDefaultUserAgent {
return
}
2022-03-20 17:38:46 +01:00
_ , 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
2024-02-24 21:26:55 +01:00
func fileFromEmbedFS ( name string , fs * embed . FS ) ( * File , error ) {
_ , err := fs . Open ( name )
2022-07-07 10:46:57 +02:00
if err != nil {
return nil , fmt . Errorf ( "failed to open file from embed.FS: %w" , err )
}
return & File {
2024-02-24 21:26:55 +01:00
Name : filepath . Base ( name ) ,
2022-07-07 10:46:57 +02:00
Header : make ( map [ string ] [ ] string ) ,
2024-02-24 21:26:55 +01:00
Writer : func ( writer io . Writer ) ( int64 , error ) {
file , err := fs . Open ( name )
2022-07-07 10:46:57 +02:00
if err != nil {
return 0 , err
}
2024-02-24 21:26:55 +01:00
numBytes , err := io . Copy ( writer , file )
2022-07-07 10:46:57 +02:00
if err != nil {
2024-02-24 21:26:55 +01:00
_ = file . Close ( )
return numBytes , fmt . Errorf ( "failed to copy file to io.Writer: %w" , err )
2022-07-07 10:46:57 +02:00
}
2024-02-24 21:26:55 +01:00
return numBytes , file . Close ( )
2022-07-07 10:46:57 +02:00
} ,
} , nil
}
2022-03-14 10:29:53 +01:00
// fileFromFS returns a File pointer from a given file in the system's file system
2024-02-24 21:26:55 +01:00
func fileFromFS ( name string ) * File {
_ , err := os . Stat ( name )
2022-03-15 11:56:21 +01:00
if err != nil {
return nil
}
2022-03-14 10:29:53 +01:00
return & File {
2024-02-24 21:26:55 +01:00
Name : filepath . Base ( name ) ,
2022-03-14 10:29:53 +01:00
Header : make ( map [ string ] [ ] string ) ,
2024-02-24 21:26:55 +01:00
Writer : func ( writer io . Writer ) ( int64 , error ) {
file , err := os . Open ( name )
2022-03-14 10:29:53 +01:00
if err != nil {
2022-03-18 17:08:05 +01:00
return 0 , err
2022-03-14 10:29:53 +01:00
}
2024-02-24 21:26:55 +01:00
numBytes , err := io . Copy ( writer , file )
2022-03-18 17:08:05 +01:00
if err != nil {
2024-02-24 21:26:55 +01:00
_ = file . Close ( )
return numBytes , fmt . Errorf ( "failed to copy file to io.Writer: %w" , err )
2022-03-14 10:29:53 +01:00
}
2024-02-24 21:26:55 +01:00
return numBytes , file . 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
2024-02-24 21:26:55 +01:00
func fileFromReader ( name string , reader io . Reader ) ( * File , error ) {
d , err := io . ReadAll ( reader )
2023-01-31 17:38:31 +01:00
if err != nil {
2023-12-24 17:04:55 +01:00
return & File { } , err
2023-01-31 17:38:31 +01:00
}
2024-02-24 21:26:55 +01:00
byteReader := bytes . NewReader ( d )
2022-03-14 10:29:53 +01:00
return & File {
2024-02-24 21:26:55 +01:00
Name : name ,
2023-01-31 17:38:31 +01:00
Header : make ( map [ string ] [ ] string ) ,
2024-02-24 21:26:55 +01:00
Writer : func ( writer io . Writer ) ( int64 , error ) {
readBytes , copyErr := io . Copy ( writer , byteReader )
if copyErr != nil {
return readBytes , copyErr
2023-01-31 17:38:31 +01:00
}
2024-02-24 21:26:55 +01:00
_ , copyErr = byteReader . Seek ( 0 , io . SeekStart )
return readBytes , copyErr
2023-01-31 17:38:31 +01:00
} ,
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
2024-02-24 21:26:55 +01:00
func fileFromReadSeeker ( name string , reader io . ReadSeeker ) * File {
2023-01-31 17:38:31 +01:00
return & File {
2024-02-24 21:26:55 +01:00
Name : name ,
2022-03-14 10:29:53 +01:00
Header : make ( map [ string ] [ ] string ) ,
2024-02-24 21:26:55 +01:00
Writer : func ( writer io . Writer ) ( int64 , error ) {
readBytes , err := io . Copy ( writer , reader )
2022-03-18 17:08:05 +01:00
if err != nil {
2024-02-24 21:26:55 +01:00
return readBytes , err
2022-03-14 10:29:53 +01:00
}
2024-02-24 21:26:55 +01:00
_ , err = reader . Seek ( 0 , io . SeekStart )
return readBytes , 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
2024-02-24 21:26:55 +01:00
func fileFromHTMLTemplate ( name string , tpl * ht . Template , data interface { } ) ( * File , error ) {
if tpl == nil {
2024-08-16 10:16:53 +02:00
return nil , errors . New ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2024-02-24 21:26:55 +01:00
buffer := bytes . Buffer { }
if err := tpl . Execute ( & buffer , data ) ; err != nil {
2022-06-03 12:27:26 +02:00
return nil , fmt . Errorf ( errTplExecuteFailed , err )
2022-06-03 10:40:54 +02:00
}
2024-02-24 21:26:55 +01:00
return fileFromReader ( name , & buffer )
2022-06-03 10:40:54 +02:00
}
// fileFromTextTemplate returns a File pointer form a given text/template.Template
2024-02-24 21:26:55 +01:00
func fileFromTextTemplate ( name string , tpl * tt . Template , data interface { } ) ( * File , error ) {
if tpl == nil {
2024-08-16 10:16:53 +02:00
return nil , errors . New ( errTplPointerNil )
2022-06-03 11:08:33 +02:00
}
2024-02-24 21:26:55 +01:00
buffer := bytes . Buffer { }
if err := tpl . Execute ( & buffer , data ) ; err != nil {
2022-06-03 12:27:26 +02:00
return nil , fmt . Errorf ( errTplExecuteFailed , err )
2022-06-01 16:49:34 +02:00
}
2024-02-24 21:26:55 +01:00
return fileFromReader ( name , & buffer )
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
2024-02-24 21:26:55 +01:00
func getEncoder ( enc Encoding ) mime . WordEncoder {
switch enc {
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
2024-02-24 21:26:55 +01:00
func writeFuncFromBuffer ( buffer * bytes . Buffer ) func ( io . Writer ) ( int64 , error ) {
writeFunc := func ( w io . Writer ) ( int64 , error ) {
numBytes , err := w . Write ( buffer . Bytes ( ) )
return int64 ( numBytes ) , err
2022-06-03 10:40:54 +02:00
}
2024-02-24 21:26:55 +01:00
return writeFunc
2022-06-03 10:40:54 +02:00
}