We understand that ABP Commercial/Pro provides the Text Template Management module to view, edit, replace, and manage seeded text/email templates. However, our requirement is not only template management. What we seem to be missing is an event-driven notification management layer, where the Host admin can manage which system events generate email notifications.
Our business requirements are:
- Define and maintain a set of supported notification events.
- Associate each notification event with an existing ABP text template.
- Store notification settings at the Host level, including whether email delivery is enabled or disabled for each event.
- When a notification event is triggered, verify the Host-level setting before sending an email.
- If email delivery is enabled, render the associated ABP text template using runtime values such as tenant, user, and event-specific data.
- Send the rendered email through ABP’s existing email sending infrastructure.
3 Answer(s)
-
0
Hi,
ABP doesn't ship a dedicated "event-driven notification management" module that wires events to templates with a host-level on/off switch. But every building block you need is already in the framework, and composing them takes only a thin coordinator class. Here is the pattern we recommend.
1. Define your notification events as constants
public static class AppNotificationEvents { public const string UserRegistered = "App.Notifications.UserRegistered"; public const string OrderPlaced = "App.Notifications.OrderPlaced"; }2. Register a host-only setting per event (the on/off switch)
WithProviders("G")(=GlobalSettingValueProvider.ProviderName) restricts the setting to the host level, so tenants cannot override it.public class AppNotificationSettingDefinitionProvider : SettingDefinitionProvider { public override void Define(ISettingDefinitionContext context) { context.Add(new SettingDefinition( $"{AppNotificationEvents.UserRegistered}.EmailEnabled", defaultValue: "true", isVisibleToClients: true) .WithProviders(GlobalSettingValueProvider.ProviderName)); context.Add(new SettingDefinition( $"{AppNotificationEvents.OrderPlaced}.EmailEnabled", defaultValue: "true", isVisibleToClients: true) .WithProviders(GlobalSettingValueProvider.ProviderName)); } }3. Register a text template per event
public class AppEmailTemplateDefinitionProvider : TemplateDefinitionProvider { public override void Define(ITemplateDefinitionContext context) { context.Add(new TemplateDefinition( AppNotificationEvents.UserRegistered, layout: StandardEmailTemplates.Layout, localizationResource: typeof(AppResource)) .WithVirtualFilePath("/Emailing/Templates/UserRegistered.tpl", isInlineLocalized: true)); } }Add the Text Template Management module (
Volo.Abp.TextTemplateManagement.*) to your solution. With it installed, host admins can edit the rendered content of every template through the UI;ITemplateRendererchecks the DB first and falls back to your.tplfile, so updates take effect without redeploy.4. The coordinator: setting check → render → send
This is the single place every notification flows through.
public class NotificationDispatcher : IDomainService, ITransientDependency { protected ITemplateRenderer TemplateRenderer { get; } protected ISettingProvider SettingProvider { get; } protected IEmailSender EmailSender { get; } public NotificationDispatcher( ITemplateRenderer templateRenderer, ISettingProvider settingProvider, IEmailSender emailSender) { TemplateRenderer = templateRenderer; SettingProvider = settingProvider; EmailSender = emailSender; } public virtual async Task DispatchEmailAsync( string eventCode, string to, string subject, object model, string? cultureName = null) { var enabled = await SettingProvider.GetAsync<bool>($"{eventCode}.EmailEnabled"); if (!enabled) { return; } var body = await TemplateRenderer.RenderAsync(eventCode, model, cultureName); await EmailSender.QueueAsync(to, subject, body); } }QueueAsyncpushes the email into the ABP background job queue, so the calling event handler isn't blocked by SMTP.5. Hook your business events
Any local or distributed event handler (or just any application service) calls the dispatcher:
public class UserRegisteredEmailHandler : ILocalEventHandler<UserRegisteredEto>, ITransientDependency { private readonly NotificationDispatcher _dispatcher; public UserRegisteredEmailHandler(NotificationDispatcher dispatcher) => _dispatcher = dispatcher; public Task HandleEventAsync(UserRegisteredEto eventData) => _dispatcher.DispatchEmailAsync( AppNotificationEvents.UserRegistered, eventData.Email, "Welcome", new { eventData.UserName, eventData.TenantName }); }Inside the
.tplfile you reference the model fields ({{model.user_name}},{{model.tenant_name}}, …) using Scriban or Razor depending on which engine you registered.Summary
- Static event codes give you a stable contract.
- Host-only settings give the host admin the per-event email on/off switch you described.
TemplateDefinition+ the Text Template Management module gives admins a UI to edit template content.NotificationDispatcheris the single chokepoint that checks the switch, renders, and sends — keeps your business code clean.
If you later need admins to add new event codes at runtime (without redeploy), you can promote the static class into a
NotificationEvententity (Code / TemplateName / IsEmailEnabled) and resolve it from DB inside the dispatcher — but most teams don't need that and the static approach is much simpler.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
In our notification use case, the email body is rendered from an ABP text template using runtime values such as tenant, user, and event-specific data.
Can the** email subject** also be managed dynamically through ABP Text Templates in ABP Pro?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Yes, you can.
ITemplateRenderer.RenderAsyncis just a string-template renderer — it doesn't care whether the output is HTML, plain text, or a single line for an email subject. So you simply register a second template per event for the subject and render it the same way you render the body.Register a Subject template alongside the Body template
For the subject template, don't pass a
layout— you don't want the HTML layout wrapped around a one-line subject.public class AppEmailTemplateDefinitionProvider : TemplateDefinitionProvider { public override void Define(ITemplateDefinitionContext context) { context.Add(new TemplateDefinition( $"{AppNotificationEvents.UserRegistered}.Subject", localizationResource: typeof(AppResource)) .WithVirtualFilePath("/Emailing/Templates/UserRegistered.Subject.tpl", isInlineLocalized: true)); context.Add(new TemplateDefinition( $"{AppNotificationEvents.UserRegistered}.Body", layout: StandardEmailTemplates.Layout, localizationResource: typeof(AppResource)) .WithVirtualFilePath("/Emailing/Templates/UserRegistered.Body.tpl", isInlineLocalized: true)); } }UserRegistered.Subject.tplis just one line:Welcome {{model.user_name}} to {{model.tenant_name}}!Render both in the dispatcher
public virtual async Task DispatchEmailAsync( string eventCode, string to, object model, string? cultureName = null) { var enabled = await SettingProvider.GetAsync<bool>($"{eventCode}.EmailEnabled"); if (!enabled) { return; } var subject = await TemplateRenderer.RenderAsync($"{eventCode}.Subject", model, cultureName); var body = await TemplateRenderer.RenderAsync($"{eventCode}.Body", model, cultureName); await EmailSender.QueueAsync(to, subject.Trim(), body); }subject.Trim()removes the trailing newline that template engines add to the rendered output — without it, some SMTP servers reject or fold the subject line.Admin UI behavior
The Text Template Management module lists every registered
TemplateDefinitionin its UI (it just callsITemplateDefinitionManager.GetAllAsync()under the hood). So once you register the.Subjecttemplate, the host admin sees it next to the.Bodytemplate and can edit both independently — no extra configuration, permission, or feature flag needed.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)