🧾 Scenario: Daily Invoice Email at 9 AM Local Time
Requirements:
Users are in India, UK, and US
Email should be sent at 9 AM local time
Server runs in UTC
App is built in .NET 6 Web API
🔧 Quartz.NET vs Hangfire (Quick Comparison)
| Feature | Hangfire | Quartz.NET |
|---|---|---|
| Time Zone Support | ❌ Manual | ✅ Native |
| Ideal For | Delayed jobs | Scheduled jobs |
| Dashboard | ✅ Yes | ❌ No |
| Retry/Error Handling | ✅ Yes | ⚠️ Manual |
| Distributed Setup | ✅ Yes | ✅ Yes |
🛠️ Option 1: Quartz.NET for Time-Sensitive Jobs
✅ Best for scheduled tasks at specific times in specific time zones
📦 Job Class
public class InvoiceJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
Console.WriteLine($"Invoice job running at {DateTime.UtcNow}");
// Custom logic: fetch users in IST and send emails
return Task.CompletedTask;
}
}
🗓️ Register Job in Program.cs (or a Hosted Service)
IScheduler scheduler = await StdSchedulerFactory.GetDefaultScheduler();
await scheduler.Start();
IJobDetail job = JobBuilder.Create<InvoiceJob>()
.WithIdentity("invoiceJob", "billing")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("dailyTrigger", "billing")
.WithCronSchedule("0 0 9 ? * *", x => x
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("India Standard Time")))
.Build();
await scheduler.ScheduleJob(job, trigger);
✅ This job runs daily at 9 AM IST, no matter where your server is hosted.
🛠️ Option 2: Hangfire for Delayed or Event-Based Jobs
✅ Best for fire-and-forget or delayed jobs
📦 Delayed Job Example
BackgroundJob.Schedule(() => SendEmail(userId), TimeSpan.FromMinutes(10));
📦 Recurring Job Example
RecurringJob.AddOrUpdate(
"daily-invoice",
() => SendDailyInvoices(),
Cron.Daily
);
⚠️ Time Zone Limitation: Hangfire schedules in server time.
To support multiple time zones:
Store user time zones in the database
Convert local time to UTC when scheduling
Handle logic in the job execution if needed
🏗️ Run Jobs on a Separate Server
Both Quartz.NET and Hangfire can run in a dedicated .NET 6 Worker Service:
+-------------------+ +-------------------------+
| Web API | --> | .NET 6 Worker (Jobs) |
+-------------------+ +-------------------------+
Database Job Scheduling Engine
✅ This setup improves scalability, separation of concerns, and distributed job processing.
🎯 Which Should You Use?
| Use Case | Best Choice |
|---|---|
| Time-specific jobs in user time zones | ✅ Quartz.NET |
| Delayed jobs or event-driven triggers | ✅ Hangfire |
| Need dashboard, retries, failure tracking | ✅ Hangfire |
| Hybrid needs (scheduled + triggered) | ✅ Use both |
✅ Best Practices
Store all timestamps in UTC
Convert to local time in the UI
Avoid relying on server's local time
Use Quartz.NET for precision, Hangfire for flexibility
Join the conversation! Your thoughts help the community grow.