From 609fcbacafabaaccde74fe3bc2b674bd762780ca Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Thu, 8 Oct 2026 18:19:58 +0200 Subject: [PATCH] Welcome and farewell wording: subject with the domain, calmer copy, one feedback link Both mails were landing in Junk at some recipients although the domain's SPF, DKIM and DMARC are in order, so the copy loses its marketing markers: the welcome subject names the tenant ("Your Autopilot Monitor access for {domain} is ready", no exclamation mark; the editor shows it with the domain placeholder), the heading drops its exclamation mark, "Great news" and "incredibly valuable ... don't hesitate" give way to plain sentences, the closing line is a thank-you, and both templates keep GitHub as their only feedback link (the personal LinkedIn link, a second foreign link domain, is gone). User go for the customer-visible wording. Co-Authored-By: Claude Fable 5.1 --- .../EmailServiceTests.cs | 5 ++-- .../Services/EmailService.cs | 4 +-- .../Services/EmailTemplateService.cs | 8 +++-- .../Services/EmailTemplates.cs | 30 +++++++++++-------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/EmailServiceTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/EmailServiceTests.cs index a4ca89a2..ae4b9d57 100644 --- a/src/Backend/AutopilotMonitor.Functions.Tests/EmailServiceTests.cs +++ b/src/Backend/AutopilotMonitor.Functions.Tests/EmailServiceTests.cs @@ -111,7 +111,7 @@ public sealed class EmailServiceTests Assert.Equal(EmailService.DefaultFromName, message.GetProperty("from_name").GetString()); Assert.Equal("it@contoso.invalid", message.GetProperty("to")[0].GetProperty("email").GetString()); Assert.Equal("to", message.GetProperty("to")[0].GetProperty("type").GetString()); - Assert.Equal(EmailTemplates.PreviewApprovedSubject, message.GetProperty("subject").GetString()); + Assert.Equal("Your Autopilot Monitor access for contoso.invalid is ready", message.GetProperty("subject").GetString()); Assert.Contains("contoso.invalid", message.GetProperty("html").GetString()); // A text part of our own (HtmlToText) instead of the provider's derived one, and the // RFC 3834 header on every automated mail. @@ -258,7 +258,8 @@ public sealed class EmailServiceTests // The mail is sent from a noreply address after portal access is gone, so the // feedback pointers must be external channels the recipient can actually reach. Assert.Contains("https://github.com/okieselbach/AutopilotMonitor/issues", html); - Assert.Contains("linkedin.com", html); + Assert.Contains("github.com/okieselbach/AutopilotMonitor/issues", html); + Assert.DoesNotContain("linkedin.com", html); // one feedback link only (user go 2026-10-08) } [Fact] diff --git a/src/Backend/AutopilotMonitor.Functions/Services/EmailService.cs b/src/Backend/AutopilotMonitor.Functions/Services/EmailService.cs index 223a9b6d..bf4ca957 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/EmailService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/EmailService.cs @@ -96,7 +96,7 @@ public class EmailService : IEmailService, IOffboardFarewellEmailSender } var html = await _templates.GetHtmlAsync(EmailTemplateKind.Welcome, domainName, ct); - var sent = (await SendViaMandrillAsync(toEmail, EmailTemplates.PreviewApprovedSubject, html, "welcome", ct, + var sent = (await SendViaMandrillAsync(toEmail, EmailTemplates.PreviewApprovedSubjectFor(domainName), html, "welcome", ct, HtmlToText.Convert(html), AutomatedHeaders)).Accepted; if (sent) @@ -163,7 +163,7 @@ public class EmailService : IEmailService, IOffboardFarewellEmailSender ? await _templates.GetHtmlAsync(kind, domainName, ct) : EmailTemplateService.Render(draftHtml, domainName); - var sent = (await SendViaMandrillAsync(toEmail, EmailTemplateService.Subject(kind), html, "test", ct, + var sent = (await SendViaMandrillAsync(toEmail, EmailTemplateService.Subject(kind, domainName), html, "test", ct, HtmlToText.Convert(html), AutomatedHeaders)).Accepted; if (sent) _logger.LogInformation("{Kind} test email sent to {ToEmail} (draft={IsDraft})", kind, toEmail, draftHtml is not null); diff --git a/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplateService.cs b/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplateService.cs index fe90c330..18658439 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplateService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplateService.cs @@ -65,8 +65,12 @@ public class EmailTemplateService : IEmailTemplateProvider public static string KindKey(EmailTemplateKind kind) => kind == EmailTemplateKind.Welcome ? "welcome" : "farewell"; - public static string Subject(EmailTemplateKind kind) => kind == EmailTemplateKind.Welcome - ? EmailTemplates.PreviewApprovedSubject + /// The subject as the editor shows it: the welcome one with the domain placeholder left in place. + public static string Subject(EmailTemplateKind kind) => Subject(kind, DomainPlaceholder); + + /// The subject a mail to carries. + public static string Subject(EmailTemplateKind kind, string? domainName) => kind == EmailTemplateKind.Welcome + ? EmailTemplates.PreviewApprovedSubjectFor(domainName) : EmailTemplates.OffboardingFarewellSubject; /// Built-in template with the placeholder left in place (for the editor). diff --git a/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplates.cs b/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplates.cs index 19d82abb..2c700db5 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplates.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/EmailTemplates.cs @@ -9,16 +9,25 @@ namespace AutopilotMonitor.Functions.Services; /// public static class EmailTemplates { - public const string PreviewApprovedSubject = "Your Autopilot Monitor access is ready!"; + /// + /// Welcome subject with the tenant's domain in place of {domain}: a concrete, calm subject + /// line (no exclamation mark, no generic "your access") is what a first mail to a new + /// organisation needs to stay out of the junk folder. + /// + public const string PreviewApprovedSubject = "Your Autopilot Monitor access for {domain} is ready"; + + public static string PreviewApprovedSubjectFor(string? domainName) + => PreviewApprovedSubject.Replace("{domain}", string.IsNullOrWhiteSpace(domainName) ? "your organization" : domainName.Trim()); // ── Offboarding farewell email ──────────────────────────────────────────── // // Sent once, after the offboarding worker finishes Phase 2 (post History terminal // write). Deliberately makes no claims about what data was or wasn't deleted — // custom rules are archived and the audit history row survives, so the only durable - // statement is "the offboarding is complete". Feedback pointers go to GitHub/LinkedIn - // (same channels as the welcome email) because this is a noreply sender and the - // in-app feedback widget is gone once the tenant is offboarded. + // statement is "the offboarding is complete". The feedback pointer goes to GitHub (same + // channel as the welcome email; a second, personal link domain only costs deliverability) + // because this is a noreply sender and the in-app feedback widget is gone once the + // tenant is offboarded. public const string OffboardingFarewellSubject = "Thank you for using Autopilot Monitor"; @@ -91,7 +100,6 @@ public static class EmailTemplates

@@ -100,7 +108,7 @@ public static class EmailTemplates

This is the last email you'll receive from us. This mailbox doesn't accept - replies – please use the links above if you'd like to get in touch. + replies – please use the link above if you'd like to get in touch.

@@ -169,10 +177,10 @@ public static class EmailTemplates -

Welcome to Autopilot Monitor!

+

Welcome to Autopilot Monitor

- Great news – access for {displayDomain} has been activated and is ready to use. + Access for {displayDomain} has been activated and is ready to use. You can now sign in and start monitoring your Windows Autopilot enrollments in real time.

@@ -195,17 +203,15 @@ public static class EmailTemplates

- Your feedback is incredibly valuable and helps shape the product. If you run into issues - or have ideas for improvements, please don't hesitate to reach out: + Questions, problems or ideas for improvements are welcome:

- Thanks for being part of the journey – enjoy Autopilot Monitor! + Thank you for using Autopilot Monitor.