feat: render email templates with Razor components - #2525
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 71cd0a0585
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 700762c032
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da692f94c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
3e84201 to
054a6fd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c824cef6fa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| private readonly ILogger _logger; | ||
|
|
||
| public Mailer(IQueue<MailMessage> queue, FormattingPluginManager pluginManager, ITextSerializer serializer, AppOptions appOptions, TimeProvider timeProvider, ILogger<Mailer> logger) | ||
| public Mailer(IQueue<MailMessage> queue, IEmailTemplateRenderer templateRenderer, FormattingPluginManager pluginManager, ITextSerializer serializer, AppOptions appOptions, TimeProvider timeProvider, ILogger<Mailer> logger) |
There was a problem hiding this comment.
Restore the public six-parameter Mailer constructor
BLOCKER: Replacing the existing public constructor with this renderer-aware signature breaks source and binary compatibility for consumers that directly instantiate Mailer; rebuilt clients no longer compile, and previously compiled clients can fail with MissingMethodException. Fresh evidence after the earlier review thread is that commit c824cef6 removed the restored overload and its compatibility test, so the final change again lacks the original signature; retain it as a delegating overload.
AGENTS.md reference: AGENTS.md:L67-L67
Useful? React with 👍 / 👎.
a50de13 to
3dbbc93
Compare
Summary
Replaces the Foundation for Emails/Gulp source project and the generated, embedded Handlebars HTML with a .NET 10 Razor class library used directly by
Exceptionless.Core.HtmlRendererHandlebars.NetdependencyPackageReferenceto the email-template project; it uses the sharedMicrosoft.AspNetCore.AppframeworkEmailAppUrlBuilderBehavior
Mailercreates a strongly typed record for each email, renders it throughIEmailTemplateRenderer, and then enqueues the existingMailMessagepayload. Renderer dispatch is centralized, and generic component constraints verify each component-to-model pairing at compile time. Razor HTML-encodes interpolated content by default; application URLs andmailto:values are constructed and escaped explicitly. Mailbox addresses are parsed and canonicalized before their local and domain components are escaped separately, preserving valid mailbox syntax without allowing message-body query injection.Email action metadata is emitted as JSON-LD with source-generated
System.Text.Jsonmetadata, so subjects containing quotes cannot alter the structured-data document. The queued message shape and delivery pipeline are unchanged.Mailerhas one renderer-aware constructor; all construction sites are controlled in this repository and use DI or explicitly provide the renderer.Application links preserve the existing templates' exact
BaseURL-plus-route behavior, including the existing stack actions, organization views, project reports, account settings, and authentication pages. With the productionBaseURL, these remain clean paths such as/event/{id}; if a configuredBaseURLalready contains a hash fragment, that fragment is preserved.Critical presentation styles are inline in the Razor markup. The remaining
<style>block contains client resets and responsive media queries that cannot be represented as inline declarations, so there is no CSS-inlining build step.Rendering parity audit
The current main base (
5cb6b5910, including #2526) and this branch were rendered with identical deterministic models. All 13 representative variants were captured in Chromium at 800 px desktop and 375 px mobile: 52 source screenshots and 26 labeled side-by-side comparisons.Automated comparison of normalized visible text, decoded
hrefvalues, and parsed JSON-LD found:Text differences
to, changesbeing countingtobeing counted, and fixesconsernstoconcernslogintolog inVisual and markup differences
margin: 0 0 16pxtomargin: 16px auto 21px; callouts similarly gain top/bottom spacing, so event bodies are about 10-13 px taller in the representative renders98765) to a readable 2x2 grid; this makes that representative mobile message 66 px tallertarget="_blank"; the logo has explicit dimensions; light-only color-scheme metadata and presentation roles are addedMarginfallbacks, automatic hyphenation, and extensive Foundation/Outlook compatibility declarations are removed. Chromium parity does not prove Outlook/Gmail-client parity, so those clients remain the main residual rendering riskAny subsequent change to a template or shared email component in this PR must regenerate the affected desktop and mobile comparison images and update this audit.
Template audit matrix
login->log in; links exactBefore / after images
Contact request
Desktop
Mobile
Event notice: new critical
Desktop
Mobile
Event notice: regression
Desktop
Mobile
Organization added
Desktop
Mobile
Organization invited
Desktop
Mobile
Organization notice: monthly limit
Desktop
Mobile
Organization notice: hourly throttling
Desktop
Mobile
Organization payment failed
Desktop
Mobile
Project daily summary
Desktop
Mobile
Project daily summary: throttled/free
Desktop
Mobile
Project daily summary: not configured
Desktop
Mobile
Email verification
Desktop
Mobile
Password reset
Desktop
Mobile
Previewing
From the repository root, render the static gallery:
This writes the ignored gallery and individual messages to
artifacts/email-previews. With the Aspire AppHost running, render the gallery and also send all 13 messages to Mailpit:By default, Aspire exposes Mailpit SMTP on
localhost:1026and its web UI athttp://localhost:8026.Verification
dotnet build --no-restore --disable-build-servers -m:1— 0 warnings, 0 errorsdotnet test --project tests/Exceptionless.Tests/Exceptionless.Tests.csproj --no-restore -- --filter-class Exceptionless.Tests.Mail.MailerTests— 35 passeddotnet run --no-cache --file build/EmailTemplatePreviews.cs— 13 variants renderedmailto:input, valid mailbox syntax, regressed/non-regressed behavior, absence of unresolved Handlebars/Blazor markers, and strict URL/action contractsBreaking changes
Mailernow has one constructor that requiresIEmailTemplateRenderer; the old overload was removed because all construction sites are controlled in this repository and have been updated. No HTTP API, queued message, WebSocket, configuration, or storage contract changes.