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 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-10-08 18:19:58 +02:00
parent 7b7d35f428
commit 609fcbacaf
4 changed files with 29 additions and 18 deletions
@@ -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]
@@ -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);
@@ -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
/// <summary>The subject as the editor shows it: the welcome one with the domain placeholder left in place.</summary>
public static string Subject(EmailTemplateKind kind) => Subject(kind, DomainPlaceholder);
/// <summary>The subject a mail to <paramref name="domainName"/> carries.</summary>
public static string Subject(EmailTemplateKind kind, string? domainName) => kind == EmailTemplateKind.Welcome
? EmailTemplates.PreviewApprovedSubjectFor(domainName)
: EmailTemplates.OffboardingFarewellSubject;
/// <summary>Built-in template with the placeholder left in place (for the editor).</summary>
@@ -9,16 +9,25 @@ namespace AutopilotMonitor.Functions.Services;
/// </summary>
public static class EmailTemplates
{
public const string PreviewApprovedSubject = "Your Autopilot Monitor access is ready!";
/// <summary>
/// Welcome subject with the tenant's domain in place of <c>{domain}</c>: 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.
/// </summary>
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
<ul style=""color:#374151; font-size:14px; line-height:1.8; margin:0 0 24px; padding-left:20px;"">
<li><a href=""https://github.com/okieselbach/AutopilotMonitor/issues"" target=""_blank"" style=""color:#1e8a4c; text-decoration:underline;"">Open a GitHub Issue</a></li>
<li><a href=""https://www.linkedin.com/in/oliver-kieselbach/"" target=""_blank"" style=""color:#1e8a4c; text-decoration:underline;"">Connect on LinkedIn</a></li>
</ul>
<p style=""color:#374151; font-size:15px; line-height:1.6; margin:0 0 24px;"">
@@ -100,7 +108,7 @@ public static class EmailTemplates
<p style=""color:#6b7280; font-size:13px; line-height:1.6; margin:0;"">
This is the last email you'll receive from us. This mailbox doesn't accept
replies &ndash; please use the links above if you'd like to get in touch.
replies &ndash; please use the link above if you'd like to get in touch.
</p>
</td>
</tr>
@@ -169,10 +177,10 @@ public static class EmailTemplates
<!-- Body -->
<tr>
<td style=""padding:40px;"">
<h2 style=""color:#111827; margin:0 0 16px; font-size:20px;"">Welcome to Autopilot Monitor!</h2>
<h2 style=""color:#111827; margin:0 0 16px; font-size:20px;"">Welcome to Autopilot Monitor</h2>
<p style=""color:#374151; font-size:15px; line-height:1.6; margin:0 0 16px;"">
Great news &ndash; access for <strong>{displayDomain}</strong> has been activated and is ready to use.
Access for <strong>{displayDomain}</strong> has been activated and is ready to use.
You can now <a href=""{Constants.PortalBaseUrl}"" target=""_blank"" style=""color:#1e8a4c; text-decoration:underline;"">sign in</a> and start monitoring your Windows Autopilot enrollments in real time.
</p>
@@ -195,17 +203,15 @@ public static class EmailTemplates
<!-- Feedback -->
<p style=""color:#374151; font-size:15px; line-height:1.6; margin:0 0 12px;"">
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:
</p>
<ul style=""color:#374151; font-size:14px; line-height:1.8; margin:0 0 24px; padding-left:20px;"">
<li><a href=""https://github.com/okieselbach/AutopilotMonitor/issues"" target=""_blank"" style=""color:#1e8a4c; text-decoration:underline;"">Open a GitHub Issue</a></li>
<li><a href=""https://www.linkedin.com/in/oliver-kieselbach/"" target=""_blank"" style=""color:#1e8a4c; text-decoration:underline;"">Connect on LinkedIn</a></li>
</ul>
<p style=""color:#6b7280; font-size:14px; line-height:1.6; margin:0;"">
Thanks for being part of the journey &ndash; enjoy Autopilot Monitor!
Thank you for using Autopilot Monitor.
</p>
</td>
</tr>