From 079449e30af683c5c8115b91e53661372b86da30 Mon Sep 17 00:00:00 2001 From: Nick Misasi Date: Tue, 14 Feb 2023 10:28:48 -0500 Subject: [PATCH] [MM-48528] Add support for SendGrid X-SMTPAPI header in email sends (#22296) * Add support for SendGrid X-SMTPAPI header for categorizing sent emails to track performance * Implement feedback * Remove unnecessary struct --------- Co-authored-by: Mattermost Build --- app/admin.go | 2 +- app/admin_advisor.go | 2 +- app/email/email.go | 97 +++++++++++++++++------------ app/email/email_batching.go | 2 +- app/email/mocks/ServiceInterface.go | 10 +-- app/email/service.go | 2 +- app/notification_email.go | 2 +- app/security_update_check.go | 2 +- shared/mail/mail.go | 15 ++++- shared/mail/mail_test.go | 6 +- 10 files changed, 83 insertions(+), 57 deletions(-) diff --git a/app/admin.go b/app/admin.go index 5d7556d65b..603d63170f 100644 --- a/app/admin.go +++ b/app/admin.go @@ -195,7 +195,7 @@ func (a *App) TestEmail(userID string, cfg *model.Config) *model.AppError { T := i18n.GetUserTranslations(user.Locale) license := a.Srv().License() mailConfig := a.Srv().MailServiceConfig() - if err := mail.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), mailConfig, license != nil && *license.Features.Compliance, "", "", "", ""); err != nil { + if err := mail.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), mailConfig, license != nil && *license.Features.Compliance, "", "", "", "", ""); err != nil { return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError) } diff --git a/app/admin_advisor.go b/app/admin_advisor.go index 487443d860..cd2d659935 100644 --- a/app/admin_advisor.go +++ b/app/admin_advisor.go @@ -194,7 +194,7 @@ func (a *App) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User, return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError) } - if err := mail.SendMailUsingConfig(model.MmSupportAdvisorAddress, subject, body, mailConfig, false, "", "", "", sender.Email); err != nil { + if err := mail.SendMailUsingConfig(model.MmSupportAdvisorAddress, subject, body, mailConfig, false, "", "", "", sender.Email, "NotifyAndSetWarnMetricAck"); err != nil { return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError) } } diff --git a/app/email/email.go b/app/email/email.go index c164b0cbc6..5891a7895e 100644 --- a/app/email/email.go +++ b/app/email/email.go @@ -27,6 +27,15 @@ import ( const serverInactivityHours = 100 +// Returns category if enabled is true (default false) +// If "" is returned when enabled is false, the category headers aren't attached to the email +func getSendGridCategory(category string, enabled bool) string { + if enabled { + return category + } + return "" +} + func (es *Service) SendChangeUsernameEmail(newUsername, email, locale, siteURL string) error { T := i18n.GetUserTranslations(locale) @@ -46,7 +55,7 @@ func (es *Service) SendChangeUsernameEmail(newUsername, email, locale, siteURL s return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "ChangeUsernameEmail"); err != nil { return err } @@ -79,7 +88,7 @@ func (es *Service) SendEmailChangeVerifyEmail(newUserEmail, locale, siteURL, tok return err } - if err := es.sendMail(newUserEmail, subject, body); err != nil { + if err := es.sendMail(newUserEmail, subject, body, "EmailChangeVerifyEmail"); err != nil { return err } @@ -105,7 +114,7 @@ func (es *Service) SendEmailChangeEmail(oldEmail, newEmail, locale, siteURL stri return err } - if err := es.sendMail(oldEmail, subject, body); err != nil { + if err := es.sendMail(oldEmail, subject, body, "EmailChangeEmail"); err != nil { return err } @@ -143,7 +152,7 @@ func (es *Service) SendVerifyEmail(userEmail, locale, siteURL, token, redirect s return err } - if err := es.sendMail(userEmail, subject, body); err != nil { + if err := es.sendMail(userEmail, subject, body, "VerifyEmail"); err != nil { return err } @@ -168,7 +177,7 @@ func (es *Service) SendSignInChangeEmail(email, method, locale, siteURL string) return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "SignInChangeEmail"); err != nil { return err } @@ -226,7 +235,7 @@ func (es *Service) SendWelcomeEmail(userID string, email string, verified bool, return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "WelcomeEmail"); err != nil { return err } @@ -259,11 +268,11 @@ func (es *Service) SendCloudUpgradeConfirmationEmail(userEmail, name, date, loca } if isYearly { - if err := es.SendMailWithEmbeddedFilesAndCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail, embeddedFiles); err != nil { + if err := es.SendMailWithEmbeddedFilesAndCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail, embeddedFiles, "CloudUpgradeConfirmationEmail"); err != nil { return err } } else { - if err := es.sendEmailWithCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail, "CloudUpgradeConfirmationEmail"); err != nil { return err } } @@ -304,7 +313,7 @@ func (es *Service) SendCloudWelcomeEmail(userEmail, locale, teamInviteID, workSp return err } - if err := es.sendEmailWithCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(userEmail, subject, body, *es.config().SupportSettings.SupportEmail, "CloudWelcomeEmail"); err != nil { return err } @@ -330,7 +339,7 @@ func (es *Service) SendPasswordChangeEmail(email, method, locale, siteURL string return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "PasswordChangeEmail"); err != nil { return err } @@ -355,7 +364,7 @@ func (es *Service) SendUserAccessTokenAddedEmail(email, locale, siteURL string) return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "UserAccessTokenAddedEmail"); err != nil { return err } @@ -385,7 +394,7 @@ func (es *Service) SendPasswordResetEmail(email string, token *model.Token, loca return false, err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "PasswordResetEmail"); err != nil { return false, err } @@ -415,7 +424,7 @@ func (es *Service) SendMfaChangeEmail(email string, activated bool, locale, site return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "MfaChangeEmail"); err != nil { return err } @@ -501,7 +510,7 @@ func (es *Service) SendInviteEmails( mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) } - if err := es.sendMail(invite, subject, body); err != nil { + if err := es.sendMail(invite, subject, body, "InviteEmail"); err != nil { mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) if errorWhenNotSent { return SendMailError @@ -617,7 +626,7 @@ func (es *Service) SendGuestInviteEmails( mlog.Error("Failed to send invite email successfully", mlog.Err(err)) } - if nErr := es.SendMailWithEmbeddedFiles(invite, subject, body, embeddedFiles, "", "", ""); nErr != nil { + if nErr := es.SendMailWithEmbeddedFiles(invite, subject, body, embeddedFiles, "", "", "", "InviteEmail"); nErr != nil { mlog.Error("Failed to send invite email successfully", mlog.Err(nErr)) if errorWhenNotSent { return SendMailError @@ -765,7 +774,7 @@ func (es *Service) SendInviteEmailsToTeamAndChannels( mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) } - if nErr := es.SendMailWithEmbeddedFiles(invite, subject, body, embeddedFiles, "", "", ""); nErr != nil { + if nErr := es.SendMailWithEmbeddedFiles(invite, subject, body, embeddedFiles, "", "", "", "InviteEmailToTeamsAndChannels"); nErr != nil { mlog.Error("Failed to send invite email successfully", mlog.Err(nErr)) if errorWhenNotSent { inviteWithError := &model.EmailInviteWithError{ @@ -828,7 +837,7 @@ func (es *Service) SendDeactivateAccountEmail(email string, locale, siteURL stri return err } - if err := es.sendMail(email, subject, body); err != nil { // this needs to receive the header options + if err := es.sendMail(email, subject, body, "DeactivateAccountEmail"); err != nil { // this needs to receive the header options return err } @@ -839,39 +848,47 @@ func (es *Service) SendNotificationMail(to, subject, htmlBody string) error { if !*es.config().EmailSettings.SendEmailNotifications { return nil } - return es.sendMail(to, subject, htmlBody) + return es.sendMail(to, subject, htmlBody, "NotificationEmail") } -func (es *Service) sendMail(to, subject, htmlBody string) error { - return es.sendMailWithCC(to, subject, htmlBody, "") +func (es *Service) sendMail(to, subject, htmlBody, category string) error { + return es.sendMailWithCC(to, subject, htmlBody, "", category) } -func (es *Service) sendEmailWithCustomReplyTo(to, subject, htmlBody, replyToAddress string) error { +func (es *Service) sendEmailWithCustomReplyTo(to, subject, htmlBody, replyToAddress, category string) error { license := es.license() mailConfig := es.mailServiceConfig(replyToAddress) - return mail.SendMailUsingConfig(to, subject, htmlBody, mailConfig, license != nil && *license.Features.Compliance, "", "", "", "") + category = getSendGridCategory(category, license.IsCloud()) + + return mail.SendMailUsingConfig(to, subject, htmlBody, mailConfig, license != nil && *license.Features.Compliance, "", "", "", "", category) } -func (es *Service) sendMailWithCC(to, subject, htmlBody string, ccMail string) error { +func (es *Service) sendMailWithCC(to, subject, htmlBody, ccMail, category string) error { license := es.license() mailConfig := es.mailServiceConfig("") - return mail.SendMailUsingConfig(to, subject, htmlBody, mailConfig, license != nil && *license.Features.Compliance, "", "", "", ccMail) + category = getSendGridCategory(category, license.IsCloud()) + + return mail.SendMailUsingConfig(to, subject, htmlBody, mailConfig, license != nil && *license.Features.Compliance, "", "", "", ccMail, category) } -func (es *Service) SendMailWithEmbeddedFilesAndCustomReplyTo(to, subject, htmlBody, replyToAddress string, embeddedFiles map[string]io.Reader) error { +func (es *Service) SendMailWithEmbeddedFilesAndCustomReplyTo(to, subject, htmlBody, replyToAddress string, embeddedFiles map[string]io.Reader, category string) error { license := es.license() mailConfig := es.mailServiceConfig(replyToAddress) - return mail.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, mailConfig, license != nil && *license.Features.Compliance, "", "", "", "") + category = getSendGridCategory(category, license.IsCloud()) + + return mail.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, mailConfig, license != nil && *license.Features.Compliance, "", "", "", "", category) } -func (es *Service) SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string) error { +func (es *Service) SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string, category string) error { license := es.license() mailConfig := es.mailServiceConfig("") - return mail.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, mailConfig, license != nil && *license.Features.Compliance, messageID, inReplyTo, references, "") + category = getSendGridCategory(category, license.IsCloud()) + + return mail.SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, embeddedFiles, mailConfig, license != nil && *license.Features.Compliance, messageID, inReplyTo, references, "", category) } func (es *Service) InvalidateVerifyEmailTokensForUser(userID string) *model.AppError { @@ -965,7 +982,7 @@ func (es *Service) SendLicenseInactivityEmail(email, name, locale, siteURL strin return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "LicenseInactivityEmail"); err != nil { return err } @@ -993,7 +1010,7 @@ func (es *Service) SendLicenseUpForRenewalEmail(email, name, locale, siteURL, re return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "LicenseUpForRenewal"); err != nil { return err } @@ -1026,7 +1043,7 @@ func (es *Service) SendPaymentFailedEmail(email string, locale string, failedPay return false, err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "PaymentFailed"); err != nil { return false, err } @@ -1053,7 +1070,7 @@ func (es *Service) SendNoCardPaymentFailedEmail(email string, locale string, sit return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "NoCardPaymentFailed"); err != nil { return err } @@ -1083,7 +1100,7 @@ func (es *Service) SendDelinquencyEmail7(email, locale, siteURL, planName string return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency7"); err != nil { return err } @@ -1112,7 +1129,7 @@ func (es *Service) SendDelinquencyEmail14(email, locale, siteURL, planName strin return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency14"); err != nil { return err } @@ -1142,7 +1159,7 @@ func (es *Service) SendDelinquencyEmail30(email, locale, siteURL, planName strin return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency30"); err != nil { return err } @@ -1174,7 +1191,7 @@ func (es *Service) SendDelinquencyEmail45(email, locale, siteURL, planName, deli return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency45"); err != nil { return err } @@ -1207,7 +1224,7 @@ func (es *Service) SendDelinquencyEmail60(email, locale, siteURL string) error { return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency60"); err != nil { return err } @@ -1240,7 +1257,7 @@ func (es *Service) SendDelinquencyEmail75(email, locale, siteURL, planName, deli return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency75"); err != nil { return err } @@ -1272,7 +1289,7 @@ func (es *Service) SendDelinquencyEmail90(email, locale, siteURL string) error { return err } - if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail); err != nil { + if err := es.sendEmailWithCustomReplyTo(email, subject, body, *es.config().SupportSettings.SupportEmail, "Delinquency90"); err != nil { return err } @@ -1297,7 +1314,7 @@ func (es *Service) SendRemoveExpiredLicenseEmail(renewalLink, email string, loca return err } - if err := es.sendMail(email, subject, body); err != nil { + if err := es.sendMail(email, subject, body, "RemoveExpiredLicense"); err != nil { return err } diff --git a/app/email/email_batching.go b/app/email/email_batching.go index a5d47ceeb6..dd3a17d5e5 100644 --- a/app/email/email_batching.go +++ b/app/email/email_batching.go @@ -346,7 +346,7 @@ func (es *Service) sendBatchedEmailNotification(userID string, notifications []* mlog.Error("Unable to render email", mlog.Err(renderErr)) } - if nErr := es.SendMailWithEmbeddedFiles(user.Email, subject, renderedPage, embeddedFiles, "", "", ""); nErr != nil { + if nErr := es.SendMailWithEmbeddedFiles(user.Email, subject, renderedPage, embeddedFiles, "", "", "", "BatchedEmailNotification"); nErr != nil { mlog.Warn("Unable to send batched email notification", mlog.String("email", user.Email), mlog.Err(nErr)) } } diff --git a/app/email/mocks/ServiceInterface.go b/app/email/mocks/ServiceInterface.go index c42ce17982..a1ebbb5220 100644 --- a/app/email/mocks/ServiceInterface.go +++ b/app/email/mocks/ServiceInterface.go @@ -372,13 +372,13 @@ func (_m *ServiceInterface) SendLicenseUpForRenewalEmail(_a0 string, name string return r0 } -// SendMailWithEmbeddedFiles provides a mock function with given fields: to, subject, htmlBody, embeddedFiles, messageID, inReplyTo, references -func (_m *ServiceInterface) SendMailWithEmbeddedFiles(to string, subject string, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string) error { - ret := _m.Called(to, subject, htmlBody, embeddedFiles, messageID, inReplyTo, references) +// SendMailWithEmbeddedFiles provides a mock function with given fields: to, subject, htmlBody, embeddedFiles, messageID, inReplyTo, references, category +func (_m *ServiceInterface) SendMailWithEmbeddedFiles(to string, subject string, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string, category string) error { + ret := _m.Called(to, subject, htmlBody, embeddedFiles, messageID, inReplyTo, references, category) var r0 error - if rf, ok := ret.Get(0).(func(string, string, string, map[string]io.Reader, string, string, string) error); ok { - r0 = rf(to, subject, htmlBody, embeddedFiles, messageID, inReplyTo, references) + if rf, ok := ret.Get(0).(func(string, string, string, map[string]io.Reader, string, string, string, string) error); ok { + r0 = rf(to, subject, htmlBody, embeddedFiles, messageID, inReplyTo, references, category) } else { r0 = ret.Error(0) } diff --git a/app/email/service.go b/app/email/service.go index cb336c4fbf..5c9708d9ce 100644 --- a/app/email/service.go +++ b/app/email/service.go @@ -140,7 +140,7 @@ type ServiceInterface interface { SendInviteEmailsToTeamAndChannels(team *model.Team, channels []*model.Channel, senderName string, senderUserId string, senderProfileImage []byte, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, message string, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) ([]*model.EmailInviteWithError, error) SendDeactivateAccountEmail(email string, locale, siteURL string) error SendNotificationMail(to, subject, htmlBody string) error - SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string) error + SendMailWithEmbeddedFiles(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, messageID string, inReplyTo string, references string, category string) error SendLicenseUpForRenewalEmail(email, name, locale, siteURL, renewalLink string, daysToExpiration int) error SendPaymentFailedEmail(email string, locale string, failedPayment *model.FailedPayment, planName, siteURL string) (bool, error) // Cloud delinquency email sequence diff --git a/app/notification_email.go b/app/notification_email.go index 0d2490db65..a08c9dc186 100644 --- a/app/notification_email.go +++ b/app/notification_email.go @@ -130,7 +130,7 @@ func (a *App) sendNotificationEmail(c request.CTX, notification *PostNotificatio } a.Srv().Go(func() { - if nErr := a.Srv().EmailService.SendMailWithEmbeddedFiles(user.Email, html.UnescapeString(subjectText), bodyText, embeddedFiles, messageID, inReplyTo, references); nErr != nil { + if nErr := a.Srv().EmailService.SendMailWithEmbeddedFiles(user.Email, html.UnescapeString(subjectText), bodyText, embeddedFiles, messageID, inReplyTo, references, "Notification"); nErr != nil { mlog.Error("Error while sending the email", mlog.String("user_email", user.Email), mlog.Err(nErr)) } }) diff --git a/app/security_update_check.go b/app/security_update_check.go index b20d9268e9..9eab2aaa1a 100644 --- a/app/security_update_check.go +++ b/app/security_update_check.go @@ -121,7 +121,7 @@ func (s *Server) DoSecurityUpdateCheck() { mlog.Info("Sending security bulletin", mlog.String("bulletin_id", bulletin.Id), mlog.String("user_email", user.Email)) license := s.License() mailConfig := s.MailServiceConfig() - mail.SendMailUsingConfig(user.Email, i18n.T("mattermost.bulletin.subject"), string(body), mailConfig, license != nil && *license.Features.Compliance, "", "", "", "") + mail.SendMailUsingConfig(user.Email, i18n.T("mattermost.bulletin.subject"), string(body), mailConfig, license != nil && *license.Features.Compliance, "", "", "", "", "SecurityUpdateCheck") } bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id} diff --git a/shared/mail/mail.go b/shared/mail/mail.go index e0ced44a5a..cd8f0c9137 100644 --- a/shared/mail/mail.go +++ b/shared/mail/mail.go @@ -57,6 +57,7 @@ type mailData struct { messageID string inReplyTo string references string + category string } // smtpClient is implemented by an smtp.Client. See https://golang.org/pkg/net/smtp/#Client. @@ -234,7 +235,7 @@ func TestConnection(config *SMTPConfig) error { return nil } -func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, config *SMTPConfig, enableComplianceFeatures bool, messageID string, inReplyTo string, references string, ccMail string) error { +func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embeddedFiles map[string]io.Reader, config *SMTPConfig, enableComplianceFeatures bool, messageID string, inReplyTo string, references string, ccMail string, category string) error { fromMail := mail.Address{Name: config.FeedbackName, Address: config.FeedbackEmail} replyTo := mail.Address{Name: config.FeedbackName, Address: config.ReplyToAddress} @@ -250,13 +251,14 @@ func SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody string, embedded messageID: messageID, inReplyTo: inReplyTo, references: references, + category: category, } return sendMailUsingConfigAdvanced(mail, config) } -func SendMailUsingConfig(to, subject, htmlBody string, config *SMTPConfig, enableComplianceFeatures bool, messageID string, inReplyTo string, references string, ccMail string) error { - return SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, nil, config, enableComplianceFeatures, messageID, inReplyTo, references, ccMail) +func SendMailUsingConfig(to, subject, htmlBody string, config *SMTPConfig, enableComplianceFeatures bool, messageID string, inReplyTo string, references string, ccMail, category string) error { + return SendMailWithEmbeddedFilesUsingConfig(to, subject, htmlBody, nil, config, enableComplianceFeatures, messageID, inReplyTo, references, ccMail, category) } // allows for sending an email with differing MIME/SMTP recipients @@ -287,6 +289,8 @@ func sendMailUsingConfigAdvanced(mail mailData, config *SMTPConfig) error { return sendMail(c, mail, time.Now(), config) } +const SendGridXSMTPAPIHeader = "X-SMTPAPI" + func sendMail(c smtpClient, mail mailData, date time.Time, config *SMTPConfig) error { mlog.Debug("sending mail", mlog.String("to", mail.smtpTo), mlog.String("subject", mail.subject)) @@ -307,6 +311,11 @@ func sendMail(c smtpClient, mail mailData, date time.Time, config *SMTPConfig) e "Precedence": {"bulk"}, } + if mail.category != "" { + sendgridHeader := fmt.Sprintf(`{"category": %q}`, mail.category) + headers[SendGridXSMTPAPIHeader] = []string{sendgridHeader} + } + if mail.replyTo.Address != "" { headers["Reply-To"] = []string{mail.replyTo.String()} } diff --git a/shared/mail/mail_test.go b/shared/mail/mail_test.go index 202294d134..69eff70042 100644 --- a/shared/mail/mail_test.go +++ b/shared/mail/mail_test.go @@ -124,7 +124,7 @@ func TestSendMailUsingConfig(t *testing.T) { //Delete all the messages before check the sample email DeleteMailBox(emailTo) - err2 := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true, "", "", "", emailCC) + err2 := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true, "", "", "", emailCC, "") require.NoError(t, err2, "Should connect to the SMTP Server") //Check if the email was send to the right email address @@ -162,7 +162,7 @@ func TestSendMailWithEmbeddedFilesUsingConfig(t *testing.T) { "test1.png": bytes.NewReader([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")), "test2.png": bytes.NewReader([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")), } - err2 := SendMailWithEmbeddedFilesUsingConfig(emailTo, emailSubject, emailBody, embeddedFiles, cfg, true, "", "", "", emailCC) + err2 := SendMailWithEmbeddedFilesUsingConfig(emailTo, emailSubject, emailBody, embeddedFiles, cfg, true, "", "", "", emailCC, "") require.NoError(t, err2, "Should connect to the SMTP Server") //Check if the email was send to the right email address @@ -407,7 +407,7 @@ func TestSendMail(t *testing.T) { for testName, tc := range testCases { t.Run(testName, func(t *testing.T) { - mail := mailData{"", "", mail.Address{}, "", tc.replyTo, "", "", nil, nil, tc.messageID, tc.inReplyTo, tc.references} + mail := mailData{"", "", mail.Address{}, "", tc.replyTo, "", "", nil, nil, tc.messageID, tc.inReplyTo, tc.references, ""} cfg := getConfig() err = sendMail(mocm, mail, time.Now(), cfg) require.NoError(t, err)