← Back to blog

Beyond Fire-and-Forget: Scheduling Transactional Emails for Reminders and Digests

PushMail Team··4 min read

The word "transactional" in email usually means "send it right now." A user resets their password, you fire off the reset link. An order ships, you send the tracking number. The email goes out within seconds of the triggering event.

But a surprising number of emails that are functionally transactional don't fit the fire-and-forget model. Trial expiration reminders. Weekly activity digests. Renewal notices 72 hours before a charge. These are tied to a specific user action or account state, not a marketing calendar. They feel transactional. But they need to be scheduled.

Most transactional email APIs don't handle this well. They give you POST /send and nothing else. If you want to send an email three days from now, you build your own job queue, set up a cron job, or bolt on a second tool for scheduled sends.

The immediate send

PushMail's transactional endpoint works like you'd expect. A single API call sends one email to one recipient, right now:

curl -X POST https://pushmail.dev/api/v1/send \
  -H "Authorization: Bearer pm_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "to": "user@example.com",
    "templateId": 8,
    "variables": {
      "reset_url": "https://app.example.com/reset?token=xyz"
    }
  }'

The email hits the queue, gets rendered with your template variables, and goes out through SendGrid within seconds. This is the right tool for password resets, order confirmations, login codes, and anything else where latency matters.

The scheduling gap

The problem shows up when you need time-delayed sends that are still tied to individual users. Consider these scenarios:

Trial expiration reminders. A user signs up for a 14-day trial. You want to email them at day 7, day 12, and day 14. You know the exact timing at signup, but you can't send all three emails right now.

Weekly digests. Every Monday at 9am, you want to send each user a summary of their activity. The content is personalized, the timing is fixed.

Renewal notices. Three days before a subscription renews, you send a heads-up. The timing depends on when each user subscribed.

None of these are marketing emails. They are operational. But POST /v1/send only fires immediately. You need a scheduling mechanism.

Sequences for time-based drips

PushMail's sequences are designed for exactly this pattern. A sequence is a series of email steps, each with a delayHours value that controls when it fires relative to enrollment. A cron worker checks every minute for enrollments that are due.

For a trial expiration flow, you create a sequence with three steps:

  • Step 1 at 168 hours (day 7): "You're halfway through your trial" with a setup checklist
  • Step 2 at 288 hours (day 12): "Your trial ends in 2 days" with feature highlights
  • Step 3 at 336 hours (day 14): "Your trial has ended" with an upgrade CTA

When a user signs up, you enroll them in the sequence via the API. PushMail handles the rest. No cron jobs on your end. No job queues to maintain. No "did that scheduled task actually run?" anxiety at 3am.

Each step uses a template with variables that get rendered at send time, not at enrollment time. If the user updates their name between step 1 and step 2, step 2 uses the current name.

Campaign scheduling for one-off future sends

Sequences handle recurring patterns tied to individual users. But sometimes you need a one-off send at a specific date and time. A product launch announcement going out next Tuesday at 10am. A scheduled maintenance notice for this Saturday.

For these, PushMail uses campaign scheduling:

curl -X POST https://pushmail.dev/api/v1/campaigns/42/schedule \
  -H "Authorization: Bearer pm_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduledAt": "2026-03-01T09:00:00Z"
  }'

The campaign sits in a scheduled state until the designated time. The cron worker picks it up, processes it in batches, and sends to the target list. You can cancel or reschedule before the send time if plans change.

This is the right tool for anything with a fixed calendar date rather than a per-user delay.

Templates and send-time rendering

Both sequences and campaigns use the same template system. Templates use {{variable}} syntax for dynamic content:

Hi {{firstName}},

Your trial for {{planName}} ends on {{trialEndDate}}.

You've created {{projectCount}} projects so far. To keep access
to all of them, upgrade before your trial expires.

Variables are resolved at send time from the contact's data and any variables you pass at enrollment or campaign creation. This matters for scheduled sends because the data is always current. If a contact's projectCount goes from 2 to 5 between enrollment and the actual send, the email reflects 5.

Unreplaced variables are removed from the rendered output. If a contact record is missing projectCount, the template doesn't render a broken {{projectCount}} string. It renders a clean gap. Design your templates to read naturally even if optional variables are absent.

Choosing the right tool

The decision tree is straightforward:

Use POST /v1/send for emails triggered by an immediate user action where latency matters. Password resets, order confirmations, login codes, email verification.

Use sequences for emails tied to a per-user timeline. Onboarding drips, trial expiration flows, renewal reminders, re-engagement series. The timing is relative to when the user was enrolled.

Use campaign scheduling for emails going to a list at a specific date and time. Product announcements, scheduled maintenance notices, weekly newsletters. The timing is absolute on the calendar.

What you would have to build otherwise

The alternative is building your own scheduling layer. You store a "send at" timestamp in your database, run a cron job every minute to check for due sends, call a transactional API for each one, handle failures and retries, and monitor the whole thing.

That works. Plenty of teams run exactly this setup. But it is infrastructure you have to build, test, deploy, and maintain. It is another failure mode in your system. And it gets more complex as you add multi-step flows with branching logic.

PushMail's sequences and campaign scheduling run on Cloudflare Workers with a cron trigger that fires every minute. The queue consumer handles retries. The template engine handles rendering. You provide the content, the timing, and the contacts. The rest is handled.

The bottom line

If your "transactional" emails include anything time-delayed, you don't need a second tool. You need an API that treats scheduling as a first-class feature alongside immediate sends. Between sequences for per-user drips and campaign scheduling for calendar-based sends, every scheduling pattern is covered without building custom infrastructure.