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 CaseExample
Daily reportsGenerate and email daily sales report
CleanupDelete temp files older than 7 days
Database maintenanceSync data every hour
Health checksVerify 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

ExpressionSchedule
0 * * * * *Every minute
0 0 * * * *Every hour
0 0 2 * * *Every day at 2:00 AM
0 0 * * * 0Every Sunday at midnight
0 */15 * * * *Every 15 minutes
0 0 9-17 * * *Every hour from 9 AM to 5 PM

CRON Reference

FieldAllowed Values
Second0-59
Minute0-59
Hour0-23
Day1-31
Month1-12
Day of week0-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


Azure Integration Hub - Beginner Level