Skip to content

Cron Email Campaigns — Automated Email Scheduling with Cron

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Cron Email Campaigns. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn cron-based email campaign scheduling: trigger transactional emails at specific times, manage newsletter delivery queues, process bounces and complaints, and monitor email delivery health with automated Cron Jobs.

What You Learn

You will learn how to use cron for email campaigns: scheduling batch email delivery, managing email queues, processing delivery reports, handling bounces, and monitoring email infrastructure health.

Why It Matters

Sending emails at the wrong time reduces engagement and increases unsubscribes. Cron enables precise scheduling: newsletters at optimal times, reminders at specific intervals, and automated follow-ups without manual triggering.

Real-World Use

DodaTech's email cron jobs send weekly newsletters every Tuesday at 10 AM, transactional email queues process every 5 minutes, bounce reports are processed every hour, and email health metrics are collected daily at 8 AM.

Email Queue Processor

import time
import random
from datetime import datetime

class EmailQueueProcessor:
    def __init__(self):
        self.queue = []
        self.sent = 0
        self.failed = 0

    def enqueue(self, to, subject, body, scheduled_time=None):
        self.queue.append({
            'to': to,
            'subject': subject,
            'body': body,
            'scheduled': scheduled_time or datetime.now(),
            'status': 'pending'
        })

    def process_batch(self, batch_size=50):
        now = datetime.now()
        batch = [e for e in self.queue if e['scheduled'] <= now and e['status'] == 'pending'][:batch_size]

        for email in batch:
            email['status'] = 'sending'
            time.sleep(random.uniform(0.01, 0.05))
            if random.random() > 0.05:
                email['status'] = 'sent'
                self.sent += 1
            else:
                email['status'] = 'failed'
                self.failed += 1

        print(f"[{now.strftime('%H:%M:%S')}] Processed {len(batch)} emails ({self.sent} sent, {self.failed} failed)")
        return len(batch)

    def get_queue_depth(self):
        pending = sum(1 for e in self.queue if e['status'] == 'pending')
        return pending

processor = EmailQueueProcessor()
for i in range(10):
    processor.enqueue(f"user{i}@example.com", f"Newsletter #{i}", "Content here")

processor.process_batch(batch_size=50)
print(f"Queue depth: {processor.get_queue_depth()}")

Expected output:

[00:00:00] Processed 10 emails (10 sent, 0 failed)
Queue depth: 0

Email Delivery Monitor

import time
from datetime import datetime, timedelta

class EmailDeliveryMonitor:
    def __init__(self):
        self.metrics = {
            'sent': 0,
            'delivered': 0,
            'bounced': 0,
            'opened': 0,
            'clicked': 0,
            'complained': 0,
        }

    def record_delivery_event(self, event_type, count=1):
        if event_type in self.metrics:
            self.metrics[event_type] += count

    def get_delivery_rate(self):
        if self.metrics['sent'] == 0:
            return 0.0
        return (self.metrics['delivered'] / self.metrics['sent']) * 100

    def get_bounce_rate(self):
        if self.metrics['sent'] == 0:
            return 0.0
        return (self.metrics['bounced'] / self.metrics['sent']) * 100

    def get_open_rate(self):
        if self.metrics['delivered'] == 0:
            return 0.0
        return (self.metrics['opened'] / self.metrics['delivered']) * 100

    def report(self):
        print(f"Email Delivery Report ({datetime.now().strftime('%Y-%m-%d %H:%M')})")
        print(f"  Sent:      {self.metrics['sent']}")
        print(f"  Delivered: {self.metrics['delivered']} ({self.get_delivery_rate():.1f}%)")
        print(f"  Bounced:   {self.metrics['bounced']} ({self.get_bounce_rate():.1f}%)")
        print(f"  Opened:    {self.metrics['opened']} ({self.get_open_rate():.1f}%)")
        print(f"  Complaints: {self.metrics['complained']}")

monitor = EmailDeliveryMonitor()
monitor.record_delivery_event('sent', 1000)
monitor.record_delivery_event('delivered', 970)
monitor.record_delivery_event('bounced', 30)
monitor.record_delivery_event('opened', 250)
monitor.report()

Expected output:

Email Delivery Report (2026-06-28 00:00)
  Sent:      1000
  Delivered: 970 (97.0%)
  Bounced:   30 (3.0%)
  Opened:    250 (25.8%)
  Complaints: 0

Common Mistakes

1. Sending Emails at Wrong Times

Sending newsletters at midnight or during weekends reduces engagement. Schedule emails during optimal times: 10 AM-2 PM Tuesday-Thursday. Use cron to schedule delivery at these times. Allow users to choose their preferred timezone.

2. No Bounce Processing

Hard bounces (invalid addresses) waste delivery resources and damage sender reputation. Process bounce reports every hour via cron. Automatically suppress hard bounces. Flag soft bounces (temporary failures) for retry with backoff.

3. Email Queue Growing Unbounded

If email sending rate is lower than enqueue rate, the queue grows indefinitely. Monitor queue depth and alert if it exceeds thresholds. Implement backpressure: pause enqueueing when queue is too deep.

4. No Rate Limiting for ESP Limits

Email service providers (SendGrid, SES) have sending limits. Implement a rate limiter that respects the ESP's limits: max emails per second, per hour, per day. Cron jobs should process at a throttled rate.

5. Sending Without Engagement Monitoring

If open rates drop below 10% or complaint rates exceed 0.1%, you risk being marked as spam. Monitor engagement metrics daily via cron. Alert on negative trends. Pause campaigns if metrics exceed thresholds.

Practice Questions

1. How do you schedule a weekly newsletter with cron?

Set a cron expression: 0 10 * * 2 for Tuesday at 10 AM. The cron job queries subscribers, generates the newsletter content, and adds to the email queue. The queue processor runs every 5 minutes to send batch emails.

2. How do you handle email bounces in cron?

Schedule a bounce processing cron job every hour. The job downloads bounce reports from the ESP, classifies bounces as hard (invalid address) or soft (temporary), suppresses hard bounces, and schedules retry for soft bounces with exponential backoff.

3. How do you rate-limit email sending in a cron job?

Implement a token bucket that respects ESP limits: 10 emails/second, 5000/hour, 50000/day. The cron job processes emails at this rate. If the daily limit is reached, remaining emails stay in the queue for the next day.

4. What email metrics should cron jobs monitor?

Track: emails sent, delivered, bounced (hard/soft), opened, clicked, complained, unsubscribed. Calculate rates: delivery rate (>95%), bounce rate (<5%), open rate (>20%), complaint rate (<0.1%). Alert on threshold breaches.

Challenge

Build an email campaign cron system: (1) campaign scheduler: weekly newsletter (Tue 10 AM), daily digest (7 AM), transactional queue (every 5 min), (2) email queue with throttling: 10 emails/sec, 5000/hour, 50000/day, (3) bounce processor: hourly, classify hard/soft, suppress hard, retry soft with 24h/72h/168h backoff, (4) engagement monitor: daily report of delivery/open/click rates, (5) suppression list: maintain and check against hard bounces and complaints, (6) monitoring: queue depth, sending rate, delivery rates, bounce rates, (7) alerting: bounce rate >5%, open rate <10%, complaint rate >0.1%, queue >10000.

FAQ

Should I send all emails immediately or use a queue?

Use a queue for reliable delivery. The cron job adds emails to the queue, and a separate processor sends them. This allows retry on failure, throttling to ESP limits, and monitoring of delivery status.

How often should the email queue processor run?

Every 1-5 minutes for transactional emails (password resets, confirmations). Every 15-60 minutes for marketing emails. Adjust based on volume and urgency.

How do I handle email sending failures?

Transient failures (ESP timeout, network): retry with backoff (5min, 15min, 60min, 4h, 24h). Permanent failures (invalid address): suppress immediately. Log all failures with ESP response codes.

What is a good open rate for scheduled emails?

20-30% is typical for newsletter emails. Transactional emails (password resets, receipts) should have 50-80% open rates. Alert if open rates drop below 10% for newsletters.

How do I respect timezones in cron email scheduling?

Store the user's preferred timezone. The cron job runs in UTC but calculates delivery time based on the user's timezone. Batch users by timezone and run separate cron jobs for each batch.

Mini Project: Email Campaign Cron System

Build a cron-based email campaign system: (1) campaign scheduler: weekly newsletter (Tue 10 AM), daily digest, transactional queue (every 5 min), re-engagement (every 30 days since last email), (2) email queue with throttling: 10 emails/sec concurrency, 5000/hour limit, 50000/day limit, (3) bounce processor: hourly, hard/soft classification, suppression list, (4) engagement monitor: daily delivery/open/click rates, complaint tracking, (5) suppression management: auto-suppress hard bounces and complaints, manual review for soft bounces, (6) rate limiter: token bucket per ESP with configurable limits, (7) monitoring: queue depth, sending rate, delivery metrics, alerting on threshold breaches.

What's Next

Now that you understand email campaign scheduling with cron, explore SSL certificate renewal automation, then learn about system health check scheduling.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro