How to Send Emails in C# / .NET (ASP.NET Core, 2026 Guide)

.NET has SmtpClient built in, but Microsoft themselves marked it as obsolete. The recommended approach is to use a third-party library for SMTP or, better yet, an API-based email provider.
This guide covers how to send emails from ASP.NET Core using API providers. HttpClient calls, dependency injection, Razor templates, and background sending with hosted services. All examples use .NET 8+ and C# 12.
SmtpClient vs API Providers
// SmtpClient - obsolete, don't use
using var client = new SmtpClient("smtp.gmail.com", 587);
client.Credentials = new NetworkCredential("you@gmail.com", "password");
await client.SendMailAsync(message);
// [Obsolete] SmtpClient doesn't support modern protocols
// API provider: one HTTP call
await httpClient.PostAsJsonAsync(
"https://api.sequenzy.com/v1/transactional/send",
new { to, subject, body }
);Pick a Provider
- Sequenzy is built for SaaS. Transactional emails, marketing campaigns, automated sequences from one API. Native Stripe integration.
- Resend is developer-friendly. Clean API. They have one-off broadcast campaigns but no automations or sequences.
- SendGrid is the enterprise option. Has an official .NET SDK. Good for high volume.
Install
# No SDK needed - uses built-in HttpClientdotnet add package Resenddotnet add package SendGridAdd your API key to appsettings.json:
{
"Email": {
"Provider": "sequenzy",
"ApiKey": "sq_your_api_key_here"
}
}{
"Email": {
"Provider": "resend",
"ApiKey": "re_your_api_key_here"
}
}{
"Email": {
"Provider": "sendgrid",
"ApiKey": "SG.your_api_key_here"
}
}Create an Email Service
using System.Net.Http.Headers;
using System.Text.Json;
public interface IEmailService
{
Task SendAsync(string to, string subject, string body);
}
public class SequenzyEmailService : IEmailService
{
private readonly HttpClient _http;
public SequenzyEmailService(HttpClient http, IConfiguration config)
{
_http = http;
_http.BaseAddress = new Uri("https://api.sequenzy.com/v1/");
_http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", config["Email:ApiKey"]);
}
public async Task SendAsync(string to, string subject, string body)
{
var response = await _http.PostAsJsonAsync("transactional/send", new
{
to,
subject,
body,
});
response.EnsureSuccessStatusCode();
}
}using Resend;
public interface IEmailService
{
Task SendAsync(string to, string subject, string html);
}
public class ResendEmailService : IEmailService
{
private readonly IResend _resend;
public ResendEmailService(IResend resend)
{
_resend = resend;
}
public async Task SendAsync(string to, string subject, string html)
{
await _resend.EmailSendAsync(new EmailMessage
{
From = "Your App <noreply@yourdomain.com>",
To = to,
Subject = subject,
HtmlBody = html,
});
}
}using SendGrid;
using SendGrid.Helpers.Mail;
public interface IEmailService
{
Task SendAsync(string to, string subject, string html);
}
public class SendGridEmailService : IEmailService
{
private readonly ISendGridClient _client;
public SendGridEmailService(ISendGridClient client)
{
_client = client;
}
public async Task SendAsync(string to, string subject, string html)
{
var from = new EmailAddress("noreply@yourdomain.com", "Your App");
var toAddress = new EmailAddress(to);
var msg = MailHelper.CreateSingleEmail(from, toAddress, subject, null, html);
var response = await _client.SendEmailAsync(msg);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"SendGrid returned {response.StatusCode}");
}
}
}Register in DI:
builder.Services.AddHttpClient<IEmailService, SequenzyEmailService>();builder.Services.AddOptions();
builder.Services.AddHttpClient<ResendClient>();
builder.Services.Configure<ResendClientOptions>(o =>
o.ApiToken = builder.Configuration["Email:ApiKey"]!);
builder.Services.AddTransient<IResend, ResendClient>();
builder.Services.AddTransient<IEmailService, ResendEmailService>();builder.Services.AddSingleton<ISendGridClient>(
new SendGridClient(builder.Configuration["Email:ApiKey"]));
builder.Services.AddTransient<IEmailService, SendGridEmailService>();Minimal API Endpoint
// Program.cs
app.MapPost("/api/email/send-welcome", async (
WelcomeRequest request,
IEmailService email) =>
{
await email.SendAsync(
request.Email,
$"Welcome, {request.Name}",
$"<h1>Welcome, {request.Name}</h1><p>Your account is ready.</p>"
);
return Results.Ok(new { sent = true });
});
record WelcomeRequest(string Email, string Name);Or with a controller:
[ApiController]
[Route("api/[controller]")]
public class EmailController : ControllerBase
{
private readonly IEmailService _email;
public EmailController(IEmailService email) => _email = email;
[HttpPost("send-welcome")]
public async Task<IActionResult> SendWelcome([FromBody] WelcomeRequest request)
{
await _email.SendAsync(
request.Email,
$"Welcome, {request.Name}",
$"<h1>Welcome, {request.Name}</h1><p>Your account is ready.</p>"
);
return Ok(new { sent = true });
}
}Razor Email Templates
Use Razor to build type-safe email templates.
// Services/EmailTemplateService.cs
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class EmailTemplateService
{
private readonly IRazorViewEngine _viewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
// ... inject dependencies
public async Task<string> RenderAsync<TModel>(string viewName, TModel model)
{
// Render a Razor view to string
// See Microsoft docs for full implementation
}
}For simpler cases, use interpolated strings or a template function:
public static class EmailTemplates
{
public static string Welcome(string name, string loginUrl) => $"""
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; background: #f6f9fc; padding: 40px 0;">
<div style="max-width: 480px; margin: 0 auto; background: #fff; padding: 40px; border-radius: 8px;">
<h1 style="font-size: 24px;">{name}</h1>
<p style="color: #374151;">Your account is ready.</p>
<a href="{loginUrl}"
style="display:inline-block; background:#f97316; color:#fff; padding:12px 24px; border-radius:6px; text-decoration:none;">
Go to Dashboard
</a>
</div>
</body>
</html>
""";
}Background Sending with Hosted Services
Use IHostedService or channels for background email processing:
using System.Threading.Channels;
public record EmailJob(string To, string Subject, string Body);
public class EmailChannel
{
private readonly Channel<EmailJob> _channel = Channel.CreateBounded<EmailJob>(100);
public ChannelWriter<EmailJob> Writer => _channel.Writer;
public ChannelReader<EmailJob> Reader => _channel.Reader;
}
public class EmailWorker : BackgroundService
{
private readonly EmailChannel _channel;
private readonly IServiceScopeFactory _scopeFactory;
public EmailWorker(EmailChannel channel, IServiceScopeFactory scopeFactory)
{
_channel = channel;
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
await foreach (var job in _channel.Reader.ReadAllAsync(ct))
{
using var scope = _scopeFactory.CreateScope();
var email = scope.ServiceProvider.GetRequiredService<IEmailService>();
try
{
await email.SendAsync(job.To, job.Subject, job.Body);
}
catch (Exception ex)
{
// Log and continue
}
}
}
}Register and use:
builder.Services.AddSingleton<EmailChannel>();
builder.Services.AddHostedService<EmailWorker>();
// In your endpoint:
app.MapPost("/api/signup", async (SignupRequest req, EmailChannel channel) =>
{
var user = await CreateUser(req);
await channel.Writer.WriteAsync(new EmailJob(
user.Email, $"Welcome, {user.Name}", EmailTemplates.Welcome(user.Name, "/dashboard")
));
return Results.Ok(new { user });
});Going to Production
1. Verify Your Domain
Add SPF, DKIM, DMARC DNS records. Required for deliverability.
2. Use User Secrets for Development
dotnet user-secrets set "Email:ApiKey" "sq_your_key"3. Add Polly for Retries
dotnet add package Microsoft.Extensions.Http.Pollybuilder.Services.AddHttpClient<IEmailService, SequenzyEmailService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))));Beyond Transactional
Sequenzy handles transactional sends, marketing campaigns, automated sequences, and subscriber management from one API. Native Stripe integration for SaaS.
Wrapping Up
- SmtpClient is obsolete - use API providers
- Dependency injection with
IEmailServicefor testability - Razor or raw string templates for HTML emails
- Background services with channels for non-blocking sends
- Polly retries for production reliability
Pick your provider, register the service, and start sending.