diff --git a/config/notifiers.go b/config/notifiers.go index 24ec7c8f33..68c411b5a6 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -33,6 +33,7 @@ var ( VSendResolved: false, }, HTML: `{{ template "email.default.html" . }}`, + Text: ``, } // DefaultEmailSubject defines the default Subject header of an Email. @@ -142,6 +143,7 @@ type EmailConfig struct { AuthIdentity string `yaml:"auth_identity,omitempty" json:"auth_identity,omitempty"` Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` HTML string `yaml:"html,omitempty" json:"html,omitempty"` + Text string `yaml:"text,omitempty" json:"text,omitempty"` RequireTLS *bool `yaml:"require_tls,omitempty" json:"require_tls,omitempty"` // Catches all undefined fields and must be empty after parsing. diff --git a/notify/impl.go b/notify/impl.go index f2283e605a..cd900550b5 100644 --- a/notify/impl.go +++ b/notify/impl.go @@ -20,9 +20,9 @@ import ( "encoding/json" "errors" "fmt" - "io" "io/ioutil" "mime" + "mime/multipart" "net" "net/http" "net/mail" @@ -40,6 +40,7 @@ import ( "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" + "net/textproto" ) type notifierConfig interface { @@ -365,23 +366,51 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { fmt.Fprintf(wc, "%s: %s\r\n", header, mime.QEncoding.Encode("utf-8", value)) } - fmt.Fprintf(wc, "Content-Type: text/html; charset=UTF-8\r\n") + buffer := &bytes.Buffer{} + multipartWriter := multipart.NewWriter(buffer) + fmt.Fprintf(wc, "Date: %s\r\n", time.Now().Format(time.RFC1123Z)) + fmt.Fprintf(wc, "Content-Type: multipart/alternative; boundary=%s\r\n", multipartWriter.Boundary()) + // TODO: Add some useful headers here, such as URL of the alertmanager // and active/resolved. fmt.Fprintf(wc, "\r\n") - // TODO(fabxc): do a multipart write that considers the plain template. + // Html template + w, err := multipartWriter.CreatePart(textproto.MIMEHeader{"Content-Type": {"text/html; charset=UTF-8"}}) + if err != nil { + return false, fmt.Errorf("creating part for html template: %s", err) + } body, err := n.tmpl.ExecuteHTMLString(n.conf.HTML, data) if err != nil { return false, fmt.Errorf("executing email html template: %s", err) } - _, err = io.WriteString(wc, body) + _, err = w.Write([]byte(body)) + if err != nil { + return true, err + } + + // Text template + // Last alternative based on recommendation in section 7.2.3 of w3 rfc1341 protocol + // https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html + w, err = multipartWriter.CreatePart(textproto.MIMEHeader{"Content-Type": {"text/plain; charset=UTF-8"}}) + if err != nil { + return false, fmt.Errorf("create part for text template: %s", err) + } + body, err = n.tmpl.ExecuteTextString(n.conf.Text, data) + if err != nil { + return false, fmt.Errorf("executing email text template: %s", err) + } + _, err = w.Write([]byte(body)) if err != nil { return true, err } + multipartWriter.Close() + + wc.Write(buffer.Bytes()) + return false, nil }