diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 4c7d4ffc97..4901c5b43c 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -16,7 +16,9 @@
-
+
+
+
@@ -72,7 +74,7 @@
-
+
diff --git a/src/Core/Platform/Mailer/BaseMail.cs b/src/Core/Platform/Mailer/BaseMail.cs
new file mode 100644
index 0000000000..5ba82699f2
--- /dev/null
+++ b/src/Core/Platform/Mailer/BaseMail.cs
@@ -0,0 +1,54 @@
+namespace Bit.Core.Platform.Mailer;
+
+#nullable enable
+
+///
+/// BaseMail describes a model for emails. It contains metadata about the email such as recipients,
+/// subject, and an optional category for processing at the upstream email delivery service.
+///
+/// Each BaseMail must have a view model that inherits from BaseMailView. The view model is used to
+/// generate the text part and HTML body.
+///
+public abstract class BaseMail where TView : BaseMailView
+{
+ ///
+ /// Email recipients.
+ ///
+ public required IEnumerable ToEmails { get; set; }
+
+ ///
+ /// The subject of the email.
+ ///
+ public abstract string Subject { get; }
+
+ ///
+ /// An optional category for processing at the upstream email delivery service.
+ ///
+ public string? Category { get; }
+
+ ///
+ /// Allows you to override and ignore the suppression list for this email.
+ ///
+ /// Warning: This should be used with caution, valid reasons are primarily account recovery, email OTP.
+ ///
+ public virtual bool IgnoreSuppressList { get; } = false;
+
+ ///
+ /// View model for the email body.
+ ///
+ public required TView View { get; set; }
+}
+
+///
+/// Each MailView consists of two body parts: a text part and an HTML part and the filename must be
+/// relative to the viewmodel and match the following pattern:
+/// - `{ClassName}.html.hbs` for the HTML part
+/// - `{ClassName}.text.hbs` for the text part
+///
+public abstract class BaseMailView
+{
+ ///
+ /// Current year.
+ ///
+ public string CurrentYear => DateTime.UtcNow.Year.ToString();
+}
diff --git a/src/Core/Platform/Mailer/HandlebarMailRenderer.cs b/src/Core/Platform/Mailer/HandlebarMailRenderer.cs
new file mode 100644
index 0000000000..49de6832b1
--- /dev/null
+++ b/src/Core/Platform/Mailer/HandlebarMailRenderer.cs
@@ -0,0 +1,80 @@
+#nullable enable
+using System.Collections.Concurrent;
+using System.Reflection;
+using HandlebarsDotNet;
+
+namespace Bit.Core.Platform.Mailer;
+
+public class HandlebarMailRenderer : IMailRenderer
+{
+ ///
+ /// Lazy-initialized Handlebars instance. Thread-safe and ensures initialization occurs only once.
+ ///
+ private readonly Lazy> _handlebarsTask = new(InitializeHandlebarsAsync, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ ///
+ /// Helper function that returns the handlebar instance.
+ ///
+ private Task GetHandlebars() => _handlebarsTask.Value;
+
+ ///
+ /// This dictionary is used to cache compiled templates in a thread-safe manner.
+ ///
+ private readonly ConcurrentDictionary>>> _templateCache = new();
+
+ public async Task<(string html, string txt)> RenderAsync(BaseMailView model)
+ {
+ var html = await CompileTemplateAsync(model, "html");
+ var txt = await CompileTemplateAsync(model, "text");
+
+ return (html, txt);
+ }
+
+ private async Task CompileTemplateAsync(BaseMailView model, string type)
+ {
+ var templateName = $"{model.GetType().FullName}.{type}.hbs";
+ var assembly = model.GetType().Assembly;
+
+ // GetOrAdd is atomic - only one Lazy will be stored per templateName.
+ // The Lazy with ExecutionAndPublication ensures the compilation happens exactly once.
+ var lazyTemplate = _templateCache.GetOrAdd(
+ templateName,
+ key => new Lazy>>(
+ () => CompileTemplateInternalAsync(assembly, key),
+ LazyThreadSafetyMode.ExecutionAndPublication));
+
+ var template = await lazyTemplate.Value;
+ return template(model);
+ }
+
+ private async Task> CompileTemplateInternalAsync(Assembly assembly, string templateName)
+ {
+ var source = await ReadSourceAsync(assembly, templateName);
+ var handlebars = await GetHandlebars();
+ return handlebars.Compile(source);
+ }
+
+ private static async Task ReadSourceAsync(Assembly assembly, string template)
+ {
+ if (assembly.GetManifestResourceNames().All(f => f != template))
+ {
+ throw new FileNotFoundException("Template not found: " + template);
+ }
+
+ await using var s = assembly.GetManifestResourceStream(template)!;
+ using var sr = new StreamReader(s);
+ return await sr.ReadToEndAsync();
+ }
+
+ private static async Task InitializeHandlebarsAsync()
+ {
+ var handlebars = Handlebars.Create();
+
+ // TODO: Do we still need layouts with MJML?
+ var assembly = typeof(HandlebarMailRenderer).Assembly;
+ var layoutSource = await ReadSourceAsync(assembly, "Bit.Core.MailTemplates.Handlebars.Layouts.Full.html.hbs");
+ handlebars.RegisterTemplate("FullHtmlLayout", layoutSource);
+
+ return handlebars;
+ }
+}
diff --git a/src/Core/Platform/Mailer/IMailRenderer.cs b/src/Core/Platform/Mailer/IMailRenderer.cs
new file mode 100644
index 0000000000..9a4c620b81
--- /dev/null
+++ b/src/Core/Platform/Mailer/IMailRenderer.cs
@@ -0,0 +1,7 @@
+#nullable enable
+namespace Bit.Core.Platform.Mailer;
+
+public interface IMailRenderer
+{
+ Task<(string html, string txt)> RenderAsync(BaseMailView model);
+}
diff --git a/src/Core/Platform/Mailer/IMailer.cs b/src/Core/Platform/Mailer/IMailer.cs
new file mode 100644
index 0000000000..84c3baf649
--- /dev/null
+++ b/src/Core/Platform/Mailer/IMailer.cs
@@ -0,0 +1,15 @@
+namespace Bit.Core.Platform.Mailer;
+
+#nullable enable
+
+///
+/// Generic mailer interface for sending email messages.
+///
+public interface IMailer
+{
+ ///
+ /// Sends an email message.
+ ///
+ ///
+ public Task SendEmail(BaseMail message) where T : BaseMailView;
+}
diff --git a/src/Core/Platform/Mailer/Mailer.cs b/src/Core/Platform/Mailer/Mailer.cs
new file mode 100644
index 0000000000..5daf80b664
--- /dev/null
+++ b/src/Core/Platform/Mailer/Mailer.cs
@@ -0,0 +1,32 @@
+using Bit.Core.Models.Mail;
+using Bit.Core.Services;
+
+namespace Bit.Core.Platform.Mailer;
+
+#nullable enable
+
+public class Mailer(IMailRenderer renderer, IMailDeliveryService mailDeliveryService) : IMailer
+{
+ public async Task SendEmail(BaseMail message) where T : BaseMailView
+ {
+ var content = await renderer.RenderAsync(message.View);
+
+ var metadata = new Dictionary();
+ if (message.IgnoreSuppressList)
+ {
+ metadata.Add("SendGridBypassListManagement", true);
+ }
+
+ var mailMessage = new MailMessage
+ {
+ ToEmails = message.ToEmails,
+ Subject = message.Subject,
+ MetaData = metadata,
+ HtmlContent = content.html,
+ TextContent = content.txt,
+ Category = message.Category,
+ };
+
+ await mailDeliveryService.SendEmailAsync(mailMessage);
+ }
+}
diff --git a/src/Core/Platform/Mailer/MailerServiceCollectionExtensions.cs b/src/Core/Platform/Mailer/MailerServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..b0847ec90f
--- /dev/null
+++ b/src/Core/Platform/Mailer/MailerServiceCollectionExtensions.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Bit.Core.Platform.Mailer;
+
+#nullable enable
+
+///
+/// Extension methods for adding the Mailer feature to the service collection.
+///
+public static class MailerServiceCollectionExtensions
+{
+ ///
+ /// Adds the Mailer services to the .
+ /// This includes the mail renderer and mailer for sending templated emails.
+ /// This method is safe to be run multiple times.
+ ///
+ /// The to add services to.
+ /// The for additional chaining.
+ public static IServiceCollection AddMailer(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ return services;
+ }
+}
diff --git a/src/Core/Platform/Mailer/README.md b/src/Core/Platform/Mailer/README.md
new file mode 100644
index 0000000000..ff62386b10
--- /dev/null
+++ b/src/Core/Platform/Mailer/README.md
@@ -0,0 +1,200 @@
+# Mailer
+
+The Mailer feature provides a structured, type-safe approach to sending emails in the Bitwarden server application. It
+uses Handlebars templates to render both HTML and plain text email content.
+
+## Architecture
+
+The Mailer system consists of four main components:
+
+1. **IMailer** - Service interface for sending emails
+2. **BaseMail** - Abstract base class defining email metadata (recipients, subject, category)
+3. **BaseMailView** - Abstract base class for email template view models
+4. **IMailRenderer** - Internal interface for rendering templates (implemented by `HandlebarMailRenderer`)
+
+## How To Use
+
+1. Define a view model that inherits from `BaseMailView` with properties for template data
+2. Create Handlebars templates (`.html.hbs` and `.text.hbs`) as embedded resources, preferably using the MJML pipeline,
+ `/src/Core/MailTemplates/Mjml`.
+3. Define an email class that inherits from `BaseMail` with metadata like subject
+4. Use `IMailer.SendEmail()` to render and send the email
+
+## Creating a New Email
+
+### Step 1: Define the Email & View Model
+
+Create a class that inherits from `BaseMailView`:
+
+```csharp
+using Bit.Core.Platform.Mailer;
+
+namespace MyApp.Emails;
+
+public class WelcomeEmailView : BaseMailView
+{
+ public required string UserName { get; init; }
+ public required string ActivationUrl { get; init; }
+}
+
+public class WelcomeEmail : BaseMail
+{
+ public override string Subject => "Welcome to Bitwarden";
+}
+```
+
+### Step 2: Create Handlebars Templates
+
+Create two template files as embedded resources next to your view model. **Important**: The file names must be located
+directly next to the `ViewClass` and match the name of the view.
+
+**WelcomeEmailView.html.hbs** (HTML version):
+
+```handlebars
+