Skip to content

feat(email): add resilient email delivery via Hangfire background jobs #348

Description

@fpindej

Problem

TemplatedEmailSender.SendSafeAsync swallows SMTP failures and logs them - the email is silently lost. For transactional emails (password reset, email verification, invitation), this means users see no error but never receive the email, with no automatic recovery.

Proposed Solution

Leverage the existing Hangfire infrastructure as a delivery queue. Enqueue email sends as background jobs to get automatic retries with exponential backoff - no custom outbox table needed.

Approach:

  • Add BackgroundEmailService : IEmailService that wraps sends in backgroundJobClient.Enqueue<SmtpEmailService>(...) instead of sending synchronously
  • SmtpEmailService stays as-is (pure SMTP send, used as the job executor)
  • Hangfire provides retry with configurable attempts and backoff (e.g. [AutomaticRetry(Attempts = 5)])
  • Failed emails visible in the Hangfire dashboard as failed jobs (manual retry possible)
  • DI registration: when both Email:Enabled and JobScheduling:Enabled are true, register BackgroundEmailService; otherwise fall back to SmtpEmailService (direct) or NoOpEmailService (disabled)

Benefits over a custom outbox:

  • Zero new infrastructure - Hangfire's PostgreSQL storage is the outbox
  • Retry, backoff, dead-letter, and dashboard visibility already built in
  • Consistent with existing job patterns in the codebase

Affected Areas

  • MyProject.Infrastructure/Features/Email/Services/ - new BackgroundEmailService
  • MyProject.Infrastructure/Features/Email/Extensions/ServiceCollectionExtensions.cs - conditional registration
  • MyProject.Component.Tests/Services/ - BackgroundEmailServiceTests

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend (.NET)featureNew feature or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions