Add per-email sender name for compliance page emails

When an email has a sender name set (the organization name), the
mailer composes the From header as "OrgName via Probo" instead of
the default global sender name. This gives compliance page
recipients clearer context about which organization is contacting
them.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-03-22 10:56:51 +01:00
parent 3bb27fe8f1
commit 2f8edfb6be
9 changed files with 72 additions and 14 deletions

View File

@@ -31,6 +31,7 @@ type (
ID gid.GID `db:"id"`
RecipientEmail string `db:"recipient_email"`
RecipientName string `db:"recipient_name"`
SenderName *string `db:"sender_name"`
ReplyTo *mail.Addr `db:"reply_to"`
UnsubscribeURL *string `db:"unsubscribe_url"`
MailingListUpdateID *gid.GID `db:"mailing_list_update_id"`
@@ -51,6 +52,7 @@ type (
Emails []*Email
EmailOptions struct {
SenderName *string
ReplyTo *mail.Addr
UnsubscribeURL *string
MailingListUpdateID *gid.GID
@@ -91,6 +93,7 @@ func NewEmail(
}
if opts != nil {
e.SenderName = opts.SenderName
e.ReplyTo = opts.ReplyTo
e.UnsubscribeURL = opts.UnsubscribeURL
e.MailingListUpdateID = opts.MailingListUpdateID
@@ -108,7 +111,9 @@ INSERT INTO emails (
id,
recipient_email,
recipient_name,
reply_to, unsubscribe_url,
sender_name,
reply_to,
unsubscribe_url,
mailing_list_update_id,
subject,
text_body,
@@ -123,6 +128,7 @@ VALUES (
@id,
@recipient_email,
@recipient_name,
@sender_name,
@reply_to,
@unsubscribe_url,
@mailing_list_update_id,
@@ -141,6 +147,7 @@ VALUES (
"id": e.ID,
"recipient_email": e.RecipientEmail,
"recipient_name": e.RecipientName,
"sender_name": e.SenderName,
"reply_to": e.ReplyTo,
"unsubscribe_url": e.UnsubscribeURL,
"mailing_list_update_id": e.MailingListUpdateID,
@@ -172,6 +179,7 @@ func (emails Emails) BulkInsert(
e.ID,
e.RecipientEmail,
e.RecipientName,
e.SenderName,
e.ReplyTo,
e.UnsubscribeURL,
e.MailingListUpdateID,
@@ -186,7 +194,7 @@ func (emails Emails) BulkInsert(
_, err := conn.CopyFrom(
ctx,
pgx.Identifier{"emails"},
[]string{"id", "recipient_email", "recipient_name", "reply_to", "unsubscribe_url", "mailing_list_update_id", "subject", "text_body", "html_body", "created_at", "updated_at"},
[]string{"id", "recipient_email", "recipient_name", "sender_name", "reply_to", "unsubscribe_url", "mailing_list_update_id", "subject", "text_body", "html_body", "created_at", "updated_at"},
pgx.CopyFromRows(rows),
)
return err
@@ -198,7 +206,7 @@ func (e *Email) LoadNextPendingForUpdateSkipLocked(
) error {
q := `
SELECT
id, recipient_email, recipient_name, reply_to, unsubscribe_url, mailing_list_update_id, subject, text_body, html_body,
id, recipient_email, recipient_name, sender_name, reply_to, unsubscribe_url, mailing_list_update_id, subject, text_body, html_body,
status, processing_started_at, attempt_count, max_attempts,
last_attempted_at, last_error, created_at, updated_at, sent_at
FROM emails

View File

@@ -0,0 +1 @@
ALTER TABLE emails ADD COLUMN sender_name text;

View File

@@ -238,8 +238,9 @@ func (w *CompletionCertificateWorker) generateCertificate(
scope coredata.Scoper,
) (*coredata.Email, coredata.EmailAttachments, error) {
var (
events = coredata.ElectronicSignatureEvents{}
signedFile = coredata.File{}
events = coredata.ElectronicSignatureEvents{}
signedFile = coredata.File{}
organization = coredata.Organization{}
)
if err := w.pg.WithConn(
@@ -253,6 +254,10 @@ func (w *CompletionCertificateWorker) generateCertificate(
return fmt.Errorf("cannot load signed file: %w", err)
}
if err := organization.LoadByID(ctx, conn, scope, signature.OrganizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
return nil
},
); err != nil {
@@ -322,7 +327,9 @@ func (w *CompletionCertificateWorker) generateCertificate(
subject,
textBody,
htmlBody,
nil,
&coredata.EmailOptions{
SenderName: new(organization.Name),
},
)
attachments := coredata.EmailAttachments{

View File

@@ -583,13 +583,20 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques
return fmt.Errorf("cannot render magic link email: %w", err)
}
var emailOpts *coredata.EmailOptions
if req.CompliancePageID != nil {
emailOpts = &coredata.EmailOptions{
SenderName: new(organization.Name),
}
}
magicLinkEmail := coredata.NewEmail(
fullName,
req.Email,
subject,
textBody,
htmlBody,
nil,
emailOpts,
)
if err := magicLinkEmail.Insert(ctx, tx); err != nil {

View File

@@ -202,9 +202,14 @@ func (w *SendingWorker) sendAndCommit(
return fmt.Errorf("cannot load email attachments: %w", err)
}
fromName := w.senderName
if email.SenderName != nil {
fromName = *email.SenderName + " via " + w.senderName
}
mail := enmime.Builder().
Subject(email.Subject).
From(w.senderName, w.senderEmail).
From(fromName, w.senderEmail).
To(email.RecipientName, email.RecipientEmail).
Text([]byte(email.TextBody))

View File

@@ -708,6 +708,7 @@ func (s *Service) CreateUpdateEmails(
textBody,
htmlBody,
&coredata.EmailOptions{
SenderName: new(orgName),
ReplyTo: replyTo,
UnsubscribeURL: &unsubscribeURL,
MailingListUpdateID: &mailingListUpdateID,
@@ -752,7 +753,18 @@ func (s *Service) buildConfirmationMail(
return nil, fmt.Errorf("cannot render subscription confirmation email: %w", err)
}
return coredata.NewEmail(fullName, email, subject, textBody, htmlBody, &coredata.EmailOptions{ReplyTo: replyTo, UnsubscribeURL: &unsubscribeURL}), nil
return coredata.NewEmail(
fullName,
email,
subject,
textBody,
htmlBody,
&coredata.EmailOptions{
SenderName: new(orgName),
ReplyTo: replyTo,
UnsubscribeURL: &unsubscribeURL,
},
), nil
}
func (s *Service) buildUnsubscriptionMail(
@@ -772,7 +784,17 @@ func (s *Service) buildUnsubscriptionMail(
return nil, fmt.Errorf("cannot render unsubscription email: %w", err)
}
return coredata.NewEmail(fullName, email, subject, textBody, htmlBody, &coredata.EmailOptions{ReplyTo: replyTo}), nil
return coredata.NewEmail(
fullName,
email,
subject,
textBody,
htmlBody,
&coredata.EmailOptions{
SenderName: new(orgName),
ReplyTo: replyTo,
},
), nil
}
func (s *Service) buildUnsubscribeURL(mailingListID gid.GID, email mail.Addr) (string, error) {

View File

@@ -693,7 +693,9 @@ func (s *DocumentService) SendSigningNotifications(
subject,
textBody,
htmlBody,
nil,
&coredata.EmailOptions{
SenderName: new(organization.Name),
},
)
if err := email.Insert(ctx, tx); err != nil {

View File

@@ -366,7 +366,9 @@ func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Con
subject,
textBody,
htmlBody,
nil,
&coredata.EmailOptions{
SenderName: new(organization.Name),
},
)
if err := accessEmail.Insert(ctx, tx); err != nil {

View File

@@ -450,7 +450,9 @@ func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Co
subject,
textBody,
htmlBody,
nil,
&coredata.EmailOptions{
SenderName: new(organization.Name),
},
)
if err := accessEmail.Insert(ctx, tx); err != nil {
@@ -585,7 +587,9 @@ func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail(
subject,
textBody,
htmlBody,
nil,
&coredata.EmailOptions{
SenderName: new(organization.Name),
},
)
if err := accessEmail.Insert(ctx, tx); err != nil {