Skip to content

SendGrid Email Scheduling — Delaying Email Delivery with send_at

DodaTech Updated 2026-06-28 4 min read

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

SendGrid email scheduling allows you to set a future delivery time using the send_at parameter, enabling delayed sends, timezone-aware delivery, and drip campaign scheduling without maintaining your own scheduling infrastructure.

What You'll Learn

  • How to schedule emails with send_at
  • How to schedule batch sends
  • Timezone-aware scheduling best practices

Why It Matters

Scheduling emails from your application requires background job queues, Cron Jobs, or worker processes. SendGrid's built-in scheduling handles this server-side, reducing application complexity and ensuring delivery even if your application is down at the scheduled time.

Real-World Use

DodaTech uses email scheduling for drip campaigns: welcome email sent immediately, day 1 follow-up scheduled 24 hours later, day 3 offer scheduled, and day 7 check-in scheduled. All scheduling is handled by SendGrid, freeing the application from managing timing logic.

from datetime import datetime, timedelta
import time
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail

def schedule_welcome_drip(user_email, user_name):
    sg = SendGridAPIClient(SENDGRID_API_KEY)
    now = int(time.time())

    # Email 1: Send immediately
    email1 = Mail(
        from_email='noreply@dodatech.com',
        to_emails=user_email,
        subject=f'Welcome to DodaTech, {user_name}!',
        html_content='<h1>Welcome!</h1><p>Thanks for joining.</p>'
    )
    sg.send(email1)

    # Email 2: Schedule for 24 hours later
    email2 = Mail(
        from_email='noreply@dodatech.com',
        to_emails=user_email,
        subject='Getting Started with DodaTech',
        html_content='<h1>Tip 1: Complete Your Profile</h1>'
    )
    email2.send_at = now + 86400  # 24 hours
    sg.send(email2)

    # Email 3: Schedule for 3 days later
    email3 = Mail(
        from_email='offers@dodatech.com',
        to_emails=user_email,
        subject='Special Offer Inside!',
        html_content='<h1>Exclusive 30-day trial</h1>'
    )
    email3.send_at = now + 259200  # 3 days
    sg.send(email3)

    print(f"Scheduled 3 emails for {user_email}")

Batch Scheduling

def schedule_batch_campaign(recipients, template_id, template_data, delay_hours=0):
    """Schedule a campaign for multiple recipients with optional delay"""
    sg = SendGridAPIClient(SENDGRID_API_KEY)
    now = int(time.time())
    results = []

    for recipient in recipients:
        message = Mail(
            from_email='campaign@dodatech.com',
            to_emails=recipient['email']
        )
        message.template_id = template_id
        message.dynamic_template_data = {
            **template_data,
            'userName': recipient['name']
        }

        if delay_hours > 0:
            message.send_at = now + (delay_hours * 3600)

        try:
            response = sg.send(message)
            results.append({
                'email': recipient['email'],
                'status': 'scheduled' if delay_hours > 0 else 'sent',
                'send_at': message.send_at if hasattr(message, 'send_at') else 'immediate'
            })
        except Exception as e:
            results.append({
                'email': recipient['email'],
                'status': 'error',
                'error': str(e)
            })

    return results

Timezone-Aware Scheduling

from datetime import datetime
import pytz

def schedule_timezone_aware(email, user_timezone_str, preferred_hour=10):
    """Schedule email at a preferred hour in the user's timezone"""
    user_tz = pytz.timezone(user_timezone_str)
    now_utc = datetime.now(pytz.UTC)
    now_user = now_utc.astimezone(user_tz)

    # Calculate next occurrence of preferred_hour in user's timezone
    if now_user.hour < preferred_hour:
        schedule_today = now_user.replace(
            hour=preferred_hour, minute=0, second=0, microsecond=0
        )
    else:
        schedule_today = now_user.replace(
            hour=preferred_hour, minute=0, second=0, microsecond=0
        ) + timedelta(days=1)

    # Convert back to UTC timestamp
    schedule_utc = schedule_today.astimezone(pytz.UTC)
    send_at = int(schedule_utc.timestamp())

    message = Mail(
        from_email='noreply@dodatech.com',
        to_emails=email,
        subject='Your Daily Digest',
        html_content='<h1>Daily Summary</h1>'
    )
    message.send_at = send_at

    sg = SendGridAPIClient(SENDGRID_API_KEY)
    sg.send(message)
    print(f"Scheduled for {schedule_today} in {user_timezone_str}")

Common Mistakes

1. Scheduling Too Far in Advance

SendGrid allows scheduling up to 72 hours in advance. Emails scheduled beyond that are rejected.

2. Using Local Time Instead of UTC

The send_at parameter expects a Unix timestamp in UTC. Sending a local time timestamp results in wrong delivery time.

3. Not Handling Past Timestamps

If send_at is in the past, SendGrid sends the email immediately. Always validate that the timestamp is in the future.

4. Scheduling Without Confirmation

Scheduled emails are queued and cannot be canceled via the API. Implement a separate cancellation mechanism using suppression groups.

5. Over-Scheduling Batch Emails

Scheduling thousands of emails at the same second can trigger rate limits. Add random delays between sends in the batch.

Practice Questions

  1. What parameter controls email scheduling in SendGrid?
  2. What format does send_at expect?
  3. How far in advance can you schedule?
  4. What happens if send_at is in the past?
  5. How do you handle timezone-aware scheduling?

Answers

  1. The send_at parameter. 2. A Unix timestamp (seconds since epoch). 3. Up to 72 hours. 4. The email is sent immediately. 5. Convert the user's preferred delivery time to a UTC Unix timestamp.

Challenge

Build an email scheduling service that: schedules emails with validation, supports timezone-aware delivery at a preferred hour, handles the 72-hour limit gracefully, and provides a scheduling dashboard showing pending, sent, and failed scheduled emails.

FAQ

What is email scheduling in SendGrid?

The ability to set a future delivery time using the send_at parameter.

How far in advance can I schedule emails?

Up to 72 hours (3 days) in advance.

Can I cancel a scheduled email?

Not via the API. Use suppression groups as a workaround.

What happens if I schedule an email in the past?

SendGrid sends it immediately.

How do I schedule emails at a user's preferred time?

Convert the user's timezone-aware time to a UTC Unix timestamp and set send_at.

Mini Project

Build a drip campaign scheduler that: creates a multi-email sequence (Day 0, Day 1, Day 3, Day 7), schedules each email using send_at with timezone awareness, tracks scheduled vs sent status, and provides a cancellation mechanism via suppression groups for users who unsubscribe mid-campaign.

What's Next

  • Learn about sandbox mode for testing without sending
  • Explore bounce handling and suppression management
  • Continue to event Webhook setup for delivery tracking

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro