Regira Office.Mail provides a unified abstraction for sending email through multiple providers. All implementations share the same IMailService interface, making mail backends interchangeable in consuming code.
| Project | Package | Backend |
|---|---|---|
Common.Office |
(transitive) | Shared abstractions, models, and DummyMailer |
Mail.SendGrid |
Regira.Office.Mail.SendGrid |
SendGrid API |
Mail.MailGun |
Regira.Office.Mail.MailGun |
Mailgun REST API |
Mail.Web |
Regira.Office.Mail.Web |
HTTP request DTOs for mail endpoints |
Mail.MSGReader |
Regira.Office.Mail.MSGReader |
Read existing .msg and .eml files |
Security.Authentication.Web |
Regira.Security.Authentication.Web |
IdentityMailer bridge to ASP.NET Identity’s IEmailSender |
<!-- SendGrid -->
<PackageReference Include="Regira.Office.Mail.SendGrid" Version="6.*" />
<!-- Mailgun -->
<PackageReference Include="Regira.Office.Mail.MailGun" Version="6.*" />
<!-- Mail.Web -->
<PackageReference Include="Regira.Office.Mail.Web" Version="6.*" />
<!-- Mail.MSGReader -->
<PackageReference Include="Regira.Office.Mail.MSGReader" Version="6.*" />
<!-- IdentityMailer (ASP.NET Identity integration) -->
<PackageReference Include="Regira.Security.Authentication.Web" Version="6.*" />
IServiceCollection services = new ServiceCollection();
IConfiguration configuration = new ConfigurationBuilder().Build();
// Register (pick one)
services.AddSendGrid(cfg => cfg.Key = configuration["Mail:SendGrid:Key"]!);
// or
services.AddMailGun(cfg =>
{
cfg.Api = configuration["Mail:MailGun:Api"]!;
cfg.Key = configuration["Mail:MailGun:Key"]!;
cfg.Domain = configuration["Mail:MailGun:Domain"]!;
});
// Use — the parameters are interface-typed, so construct the concrete models
// (the implicit string conversions don't apply to IMailAddress/IMailRecipient)
IMailService mailer = services.BuildServiceProvider().GetRequiredService<IMailService>();
await mailer.Send(
sender: new MailAddress { Email = "no-reply@example.com" },
recipients: [new MailRecipient { Email = "alice@example.com" }],
subject: "Hello",
message: "<p>Hi!</p>"
);
Both backends implement this interface.
// Parameter-based overload
Task<IMailResponse> Send(
IMailAddress sender,
IEnumerable<IMailRecipient> recipients,
string? subject,
string? message,
bool isHtml = true,
IEnumerable<INamedFile>? attachments = null,
CancellationToken cancellationToken = default);
// Full message overload
Task<IMailResponse> Send(IMessageObject message, CancellationToken cancellationToken = default);
| Property | Type | Description |
|---|---|---|
Success |
bool |
true when the provider accepted the message |
Status |
string? |
HTTP status code or provider status text |
Content |
string? |
Raw response body |
Exception |
Exception? |
Set when sending fails |
Represents a complete outgoing email.
| Property | Type | Default | Description |
|---|---|---|---|
From |
MailAddress? |
null |
Sender address |
To |
ICollection<MailRecipient> |
[] |
Recipients (To / Cc / Bcc) |
ReplyTo |
IMailAddress? |
null |
Reply-To address |
Subject |
string? |
null |
Email subject |
Body |
string? |
null |
Message body |
IsHtml |
bool |
true |
HTML vs plain text |
Attachments |
ICollection<BinaryFileItem>? |
null |
File attachments |
MessageObject exposes the concrete model types shown above; the interface-typed members of
IMessageObject (IMailAddress? From, ICollection<IMailRecipient> To, ICollection<INamedFile>? Attachments)
are implemented explicitly and convert to/from the concrete types.
| Property | Type | Description |
|---|---|---|
Email |
string |
Email address — validated on assignment |
DisplayName |
string? |
Optional display name |
MailAddress supports implicit conversion from string:
MailAddress addr = "alice@example.com";
MailAddress named = new() { Email = "alice@example.com", DisplayName = "Alice" };
ToString() returns "Alice <alice@example.com>" when DisplayName is set, or just the email.
Extends IMailAddress with a recipient type.
public enum RecipientTypes { To, Cc, Bcc }
MailRecipient to = "alice@example.com"; // implicit — defaults to RecipientTypes.To
var cc = new MailRecipient { Email = "bob@example.com", RecipientType = RecipientTypes.Cc };
var bcc = new MailRecipient { Email = "carol@example.com", RecipientType = RecipientTypes.Bcc };
| Property | Type | Description |
|---|---|---|
Key |
string |
SendGrid API key |
| Property | Type | Description |
|---|---|---|
Api |
string |
Mailgun API endpoint (e.g. https://api.mailgun.net/v3) |
Key |
string |
Mailgun API key |
Domain |
string |
Sending domain |
TestMode |
bool |
Sends with Mailgun’s o:testmode flag. Default false. |
With TestMode enabled, Mailgun validates, accepts and logs each call exactly as it would a real one, but
never delivers it to the recipient. The response is a normal success, so code and tests that check
response.Success are unaffected. Note that it suppresses delivery, not billing — message counts and
charges may still apply.
IServiceCollection services = new ServiceCollection();
// SendGrid
services.AddSendGrid(cfg => cfg.Key = "SG.xxx");
// Mailgun
services.AddMailGun(cfg =>
{
cfg.Api = "https://api.mailgun.net/v3";
cfg.Key = "key-xxx";
cfg.Domain = "mail.example.com";
});
// Mailgun, accepted and logged but never delivered — for staging hosts and test suites
// that send to real addresses
services.AddMailGun(cfg =>
{
cfg.Api = "https://api.mailgun.net/v3";
cfg.Key = "key-xxx";
cfg.Domain = "mail.example.com";
cfg.TestMode = true;
});
Both extension methods register IMailService as a transient service.
Thrown by the shared MailerBase for invalid attachments (missing file name or empty content), and by the
SendGrid and Mailgun backends when the provider returns a non-success response. The provider’s own
error body is on ResponseContent — the status code alone rarely says why a send was refused. An
unauthorized response is the exception: both backends throw a plain Exception("Not authorized") for it.
| Property | Type | Description |
|---|---|---|
MessageObject |
IMessageObject? |
The message that failed to send |
ResponseContent |
string? |
Raw provider response body |
Thrown when an invalid email address is assigned to MailAddress.Email.
| Property | Type | Description |
|---|---|---|
EmailInput |
string? |
The invalid value that was provided |
DummyMailer implements IMailService (via MailerBase) and sends nothing — it returns an empty MailResponse, so Success is false. Register it in tests to suppress actual sending:
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IMailService, DummyMailer>();
Mail.Web ships MailInput for accepting email requests over HTTP. MailInputExtensions.ToMessageObject() converts it to a domain IMessageObject.
[HttpPost]
public async Task<IActionResult> Send([FromBody] MailInput input, IMailService mailer)
{
var message = input.ToMessageObject();
var result = await mailer.Send(message);
return result.Success ? Ok() : StatusCode(502);
}
| Property | Type | Validation | Description |
|---|---|---|---|
From |
Address? |
— | Optional sender override |
To |
ICollection<Recipient>? |
[Required] |
Recipients |
ReplyTo |
Address? |
— | Reply-To address |
Subject |
string? |
[Required] |
Email subject |
Body |
string? |
— | HTML or plain text body |
IsHtml |
bool |
— | Defaults to true |
Attachments |
ICollection<Attachment>? |
— | File attachments |
Address and Recipient both support implicit conversion from a plain email string.
IdentityMailer (from the Regira.Security.Authentication.Web package, namespace
Regira.Security.Authentication.Web.Mail) bridges IMailService to the ASP.NET Identity IEmailSender interface.
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IEmailSender>(provider =>
new IdentityMailer(
provider.GetRequiredService<IMailService>(),
new IdentityMailerOptions { Sender = "no-reply@example.com" }
));