Timer Trigger Basics in Azure Functions
Overview
Timer triggers enable you to execute your Azure Functions on a predefined schedule, making them perfect for recurring tasks like generating reports, cleaning up old data, synchronizing databases, or performing health checks. Instead of manually running these tasks or setting up external schedulers, you can let Azure Functions handle the scheduling automatically using CRON expressions.
What You'll Learn
- Understanding timer triggers and their use cases
- Creating a timer-triggered function
- Writing and interpreting CRON expressions
- Configuring timer triggers with environment variables
- Working with TimerInfo properties
When to Use Timer Triggers
Timer triggers are ideal for scheduled background jobs:
- Reporting - Generate daily, weekly, or monthly reports
- Maintenance - Clean up old records, temp files, or cache
- Data Sync - Periodically sync data between systems
- Monitoring - Perform health checks and send alerts
Use Cases
| Use Case | Example |
|---|---|
| Daily reports | Generate and email daily sales report |
| Cleanup | Delete temp files older than 7 days |
| Database maintenance | Sync data every hour |
| Health checks | Verify system status every 5 minutes |
Create Timer Trigger Function
.NET Example
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
public class TimerFunction
{
private readonly ILogger<TimerFunction> _logger;
public TimerFunction(ILogger<TimerFunction> logger)
{
_logger = logger;
}
[Function("DailyCleanup")]
public void Run([TimerTrigger("0 0 2 * * *")] TimerInfo timerInfo)
{
_logger.LogInformation($"Timer executed at: {DateTime.Now}");
// Your logic here
_logger.LogInformation($"Next run: {timerInfo.Next}");
}
}
CRON Expressions
Format: second minute hour day-of-month month day-of-week
| Expression | Schedule |
|---|---|
0 * * * * * | Every minute |
0 0 * * * * | Every hour |
0 0 2 * * * | Every day at 2:00 AM |
0 0 * * * 0 | Every Sunday at midnight |
0 */15 * * * * | Every 15 minutes |
0 0 9-17 * * * | Every hour from 9 AM to 5 PM |
CRON Reference
| Field | Allowed Values |
|---|---|
| Second | 0-59 |
| Minute | 0-59 |
| Hour | 0-23 |
| Day | 1-31 |
| Month | 1-12 |
| Day of week | 0-6 (0 = Sunday) |
CRON Examples
Every day at midnight: 0 0 0 * * *
Every Monday at 6 AM: 0 0 6 * * 1
Every 30 minutes: 0 0,30 * * * *
First day of month: 0 0 1 1 * *
Every weekday 9 AM: 0 0 9 * * 1-5
Configuration in local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "...",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"MySchedule": "0 0 2 * * *"
}
}
Use custom schedule:
[TimerTrigger("%MySchedule%")] TimerInfo timerInfo
Timer Info Properties
public void Run([TimerTrigger("0 0 2 * * *")] TimerInfo timerInfo)
{
// Is this the first run?
bool isPastDue = timerInfo.IsPastDue;
// Next scheduled run
DateTime nextRun = timerInfo.Next;
// Last run (if exists)
DateTime? lastRun = timerInfo.Last;
}
Disable Timer in Local Development
[Function("DailyCleanup")]
public void Run(
[TimerTrigger("0 0 2 * * *", RunOnStartup = false)] TimerInfo timerInfo,
bool executionIsLocal = false)
{
if (executionIsLocal && !ShouldRunInLocal())
{
return;
}
// Logic...
}
Real-Time Scenarios
Scenario 1: Database Cleanup Job
Clean up old records from database:
public class DatabaseCleanupFunction
{
private readonly ILogger<DatabaseCleanupFunction> _logger;
private readonly IOrderRepository _orderRepository;
[Function("CleanupOldOrders")]
public async Task Run([TimerTrigger("0 0 2 * * *")] TimerInfo timer)
{
_logger.LogInformation("Starting database cleanup at {Time}", DateTime.UtcNow);
// Delete orders older than 90 days
var cutoffDate = DateTime.UtcNow.AddDays(-90);
var deletedCount = await _orderRepository.DeleteOrdersOlderThanAsync(cutoffDate);
_logger.LogInformation("Deleted {Count} old orders. Next run: {Next}",
deletedCount, timer.Next);
// Log to table for audit
await _auditService.LogCleanupAsync(deletedCount, cutoffDate);
}
}
Scenario 2: Generate and Email Daily Report
Create daily summary report:
public class DailyReportFunction
{
private readonly ILogger<DailyReportFunction> _logger;
private readonly IReportService _reportService;
private readonly IEmailService _emailService;
[Function("DailySalesReport")]
public async Task Run([TimerTrigger("0 0 6 * * *")] TimerInfo timer)
{
_logger.LogInformation("Generating daily sales report");
// Get yesterday's sales data
var yesterday = DateTime.UtcNow.AddDays(-1).Date;
var salesData = await _reportService.GetSalesDataAsync(yesterday);
// Generate report
var report = await _reportService.GenerateReportAsync(salesData);
// Send to distribution list
await _emailService.SendEmailAsync(new EmailRequest
{
To = "sales-team@company.com",
Subject = $"Daily Sales Report - {yesterday:yyyy-MM-dd}",
Body = report,
Attachments = new[] { report.AttachmentPath }
});
_logger.LogInformation("Report sent. Next run: {Next}", timer.Next);
}
}
Scenario 3: Sync Data Between Systems
Periodically sync data between databases:
public class DataSyncFunction
{
private readonly ILogger<DataSyncFunction> _logger;
private readonly ICrmService _crmService;
private readonly IDataWarehouseService _warehouseService;
[Function("SyncCustomerData")]
public async Task Run([TimerTrigger("0 */30 * * * *")] TimerInfo timer)
{
_logger.LogInformation("Starting customer data sync");
var lastSync = await _syncService.GetLastSyncTimeAsync();
// Get changed records since last sync
var customers = await _crmService.GetCustomersChangedSinceAsync(lastSync);
_logger.LogInformation("Found {Count} changed customers", customers.Count);
// Batch insert into data warehouse
var synced = await _warehouseService.SyncCustomersAsync(customers);
// Update sync timestamp
await _syncService.UpdateLastSyncTimeAsync(DateTime.UtcNow);
_logger.LogInformation("Synced {Count} customers. Next run: {Next}", synced, timer.Next);
}
}
Scenario 4: Health Check & Alerting
Monitor system health:
public class HealthCheckFunction
{
private readonly ILogger<HealthCheckFunction> _logger;
private readonly HttpClient _httpClient;
[Function("SystemHealthCheck")]
public async Task Run([TimerTrigger("0 */5 * * * *")] TimerInfo timer)
{
var health = new HealthCheckResult();
// Check API
try
{
var response = await _httpClient.GetAsync("https://api.example.com/health");
health.Api = response.IsSuccessStatusCode ? HealthStatus.Healthy : HealthStatus.Unhealthy;
}
catch (Exception ex)
{
health.Api = HealthStatus.Down;
_logger.LogWarning(ex, "API health check failed");
}
// Check Database
try
{
await _dbContext.Database.CanConnectAsync();
health.Database = HealthStatus.Healthy;
}
catch
{
health.Database = HealthStatus.Down;
}
// Check Service Bus
try
{
var queueInfo = await _serviceBus.GetQueueInfoAsync("orders-queue");
health.ServiceBus = queueInfo.MessageCount < 1000 ? HealthStatus.Healthy : HealthStatus.Degraded;
}
catch
{
health.ServiceBus = HealthStatus.Down;
}
// Alert if unhealthy
if (health.HasIssues)
{
await _alertService.SendAlertAsync("Health check failed", health);
}
_logger.LogInformation("Health check completed: {Status}", health);
}
}
Scenario 5: Batch Process Queue Messages
Process pending messages in queue:
public class BatchProcessorFunction
{
private readonly ILogger<BatchProcessorFunction> _logger;
private readonly ServiceBusClient _sbClient;
[Function("ProcessPendingOrders")]
public async Task Run([TimerTrigger("0 0,30 * * * *")] TimerInfo timer) // Every 30 min
{
_logger.LogInformation("Starting batch processing");
await using var receiver = _sbClient.CreateReceiver("orders-queue");
// Receive batch of messages
var messages = await receiver.ReceiveMessagesAsync(maxMessages: 100, maxWaitTime: TimeSpan.FromSeconds(30));
_logger.LogInformation("Processing {Count} messages", messages.Count);
var processed = 0;
var failed = 0;
foreach (var message in messages)
{
try
{
var order = JsonSerializer.Deserialize<Order>(message.Body);
await _orderService.ProcessAsync(order);
await receiver.CompleteMessageAsync(message);
processed++;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process message");
await receiver.AbandonMessageAsync(message);
failed++;
}
}
_logger.LogInformation("Processed: {Processed}, Failed: {Failed}, Next run: {Next}",
processed, failed, timer.Next);
}
}
Scenario 6: Archive Old Data
Move data to cold storage:
public class ArchiveFunction
{
private readonly ILogger<ArchiveFunction> _logger;
private readonly BlobServiceClient _blobClient;
[Function("ArchiveOldLogs")]
public async Task Run([TimerTrigger("0 0 3 * * 0")] TimerInfo timer) // Weekly, Sunday 3 AM
{
_logger.LogInformation("Starting weekly archive");
var archiveDate = DateTime.UtcNow.AddDays(-30);
var sourceContainer = _blobClient.GetBlobContainerClient("logs");
var archiveContainer = _blobClient.GetBlobContainerClient("archive");
await foreach (var blob in sourceContainer.GetBlobsAsync())
{
// Check if blob is older than 30 days
if (blob.Properties.CreatedOn < archiveDate)
{
// Copy to archive
var source = sourceContainer.GetBlobClient(blob.Name);
var dest = archiveContainer.GetBlobClient($"{DateTime.UtcNow:yyyy-MM}/{blob.Name}");
await dest.StartCopyFromUriAsync(source.Uri);
// Delete from source
await source.DeleteIfExistsAsync();
_logger.LogInformation("Archived: {BlobName}", blob.Name);
}
}
_logger.LogInformation("Archive completed. Next run: {Next}", timer.Next);
}
}
Scenario 7: Rate Limiting Check
Enforce rate limits periodically:
public class RateLimitCheckFunction
{
[Function("CheckRateLimits")]
public async Task Run([TimerTrigger("0 0 * * * *")] TimerInfo timer) // Every hour
{
// Reset daily usage counters
await _cache.ResetDailyLimitsAsync();
// Check for accounts exceeding limits
var exceedingAccounts = await _usageService.GetExceedingAccountsAsync();
foreach (var account in exceedingAccounts)
{
// Send notification
await _notificationService.NotifyAsync(account.UserId,
$"You have exceeded your API rate limit. Your access will be throttled.");
// Apply throttle
await _rateLimiter.ApplyThrottleAsync(account.UserId);
}
}
}
Next Steps
- Learn about Application Settings
- Explore Azure Functions with Blob Triggers
- Learn about Durable Functions for Complex Workflows
Azure Integration Hub - Beginner Level