Introduction
ASP.NET Core Identity's default UI (AddDefaultIdentity) generates email confirmation tokens and calls IEmailSender.SendEmailAsync, but the framework does not include a default email sender implementation. Without registering a concrete IEmailSender, confirmation emails are silently dropped — the registration succeeds but the user never receives the confirmation link. This also affects password reset emails and two-factor authentication codes.
Symptoms
- User registration succeeds but no confirmation email received
- Password reset link never arrives
- No errors logged during registration
IEmailSenderinjected but sends nothing- Two-factor authentication code not delivered
Common Causes
IEmailSendernot registered in DI container- SMTP configuration missing or incorrect
- Email sender throws but exception is caught silently
SendEmailAsynccalled with empty or invalid email address- Third-party email service API key not configured
Step-by-Step Fix
- 1.Implement IEmailSender with SMTP:
- 2.```csharp
- 3.public class SmtpEmailSender : IEmailSender
- 4.{
- 5.private readonly SmtpSettings _settings;
- 6.private readonly ILogger<SmtpEmailSender> _logger;
public SmtpEmailSender(IConfiguration config, ILogger<SmtpEmailSender> logger) { _settings = config.GetSection("Smtp").Get<SmtpSettings>(); _logger = logger; }
public async Task SendEmailAsync(string toEmail, string subject, string htmlMessage) { if (string.IsNullOrWhiteSpace(toEmail)) { _logger.LogWarning("Attempted to send email to empty address"); return; }
using var client = new SmtpClient(_settings.Host, _settings.Port); client.EnableSsl = _settings.EnableSsl;
if (!string.IsNullOrEmpty(_settings.Username)) { client.Credentials = new NetworkCredential(_settings.Username, _settings.Password); }
var mail = new MailMessage(_settings.FromEmail, toEmail) { Subject = subject, Body = htmlMessage, IsBodyHtml = true };
try { await client.SendMailAsync(mail); _logger.LogInformation("Confirmation email sent to {Email}", toEmail); } catch (Exception ex) { _logger.LogError(ex, "Failed to send email to {Email}", toEmail); throw; } } }
public class SmtpSettings { public string Host { get; set; } = "smtp.gmail.com"; public int Port { get; set; } = 587; public bool EnableSsl { get; set; } = true; public string? Username { get; set; } public string? Password { get; set; } public string FromEmail { get; set; } = "noreply@myapp.com"; }
// Registration in Program.cs builder.Services.AddTransient<IEmailSender, SmtpEmailSender>(); builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp")); ```
- 1.Use SendGrid for production email delivery:
- 2.```csharp
- 3.// Install: SendGrid
- 4.using SendGrid;
- 5.using SendGrid.Helpers.Mail;
public class SendGridEmailSender : IEmailSender { private readonly ISendGridClient _client; private readonly string _fromEmail; private readonly ILogger<SendGridEmailSender> _logger;
public SendGridEmailSender(IConfiguration config, ILogger<SendGridEmailSender> logger) { _client = new SendGridClient(config["SendGrid:ApiKey"]); _fromEmail = config["SendGrid:FromEmail"]; _logger = logger; }
public async Task SendEmailAsync(string toEmail, string subject, string htmlMessage) { var msg = new SendGridMessage { From = new EmailAddress(_fromEmail, "MyApp"), Subject = subject, HtmlContent = htmlMessage }; msg.AddTo(new EmailAddress(toEmail));
var response = await _client.SendEmailAsync(msg);
if (!response.IsSuccessStatusCode) { var body = await response.Body.ReadAsStringAsync(); _logger.LogError("SendGrid failed: {Status} - {Body}", response.StatusCode, body); throw new InvalidOperationException($"SendGrid failed: {response.StatusCode}"); }
_logger.LogInformation("Email sent to {Email} via SendGrid", toEmail); } }
// Program.cs builder.Services.AddTransient<IEmailSender, SendGridEmailSender>(); ```
Prevention
- Always register a concrete
IEmailSenderwhen using Identity with email confirmation - Log email send results for debugging
- Use a transactional email service (SendGrid, Mailgun, SES) in production
- Test email delivery in staging environment
- Add email queue for retry on transient failures
- Monitor email bounce and complaint rates