At one of my client projects, we needed to send a welcome email after signup, a feature nudge on day 3, and a trial expiry warning on day 12. Simple enough, right?
The first approach was a cron job that checked the database every hour. "Find all users who signed up 3 days ago and haven't received the day-3 email." It worked, until the cron skipped a run and some users got their "day 3" email on day 4. Then we added a queue so the emails wouldn't pile up during traffic spikes. Then retry logic because SendGrid occasionally returned 503s. Then the team wanted Slack alerts for trial expirations too, so we added a second delivery channel. Then timezone handling, because "day 3" means something different in Berlin and San Francisco.
Two months in, we had a notification system. We also had two months less product to show for it.
I've since seen this pattern repeat across multiple projects. Every SaaS product eventually needs to send something later — and the implementation always takes longer than anyone expects.
Here's the approach I use now. At signup, I schedule the entire onboarding sequence in one go:
curl -X POST https://pingfyr.com/api/remind \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "email", "recipients": ["newuser@example.com"], "title": "Quick tip: Set up your first project", "body": "Most teams see results within the first week. Here is how to get started...", "fire_at": "2026-03-26T09:00:00Z", "timezone": "Europe/Berlin" }'
That schedules one email for tomorrow morning, in the user's timezone. At the same signup event, I fire off the rest:
Day 3: "Did you know you can invite your team?" Day 7: "Teams like yours typically automate their first workflow by now." Day 12: "How's it going? Reply to this email — I read every one."
Four API calls at signup. No cron job polling the database. No queue infrastructure. No 2 AM debugging sessions where I'm trying to figure out if the cron even ran.
The timezone thing is worth lingering on. A user who signs up at 11 PM in Tokyo shouldn't get their "day 1" email at midnight. With Pingfyr, I schedule it for 9 AM in their timezone the next day. Each reminder is its own API call, so I can set the timing individually. The old cron approach — "check all users where signup_date was 3 days ago" — doesn't let you do this cleanly.
Trial expiry reminders are even more straightforward. When the trial starts, I already know exactly when it ends. So I schedule three reminders right then:
7 days before: "Your trial ends next week. Here's what you'll lose access to." 3 days before: "3 days left — upgrade now to keep your projects." 1 day before: "Last day. Your data will be archived tomorrow."
If the user upgrades before the trial ends, I cancel the remaining reminders via the API. No wasted notifications, no awkward "your trial is expiring" email to someone who already paid.
What I've found especially useful is mixing channels. The power users on a team — they live in Slack. A trial reminder via Slack catches them before they forget. But the occasional users? They check email. So I schedule both:
curl -X POST https://pingfyr.com/api/remind \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "slack", "recipients": ["https://hooks.slack.com/services/T00/B00/xxx"], "title": "Trial ending in 3 days", "body": "Your team's Pro trial expires on April 1. Upgrade to keep all features.", "fire_at": "2026-03-29T10:00:00Z", "timezone": "America/New_York" }'
Same concept, different channel field. I've also used the webhook channel to trigger internal logic — flagging accounts for sales outreach, updating a CRM, sending an in-app notification through our own system.
The patterns I keep coming back to:
Onboarding sequences — schedule 4 to 6 emails at signup, timed to guide users to the moment they actually get value from the product.
Failed payment recovery — schedule reminders at 1, 3, and 7 days after a failed charge. Each one a bit more urgent than the last.
Renewal reminders — 30, 14, and 3 days before renewal. Include upgrade options for users on lower tiers.
Feature adoption nudges — if someone signed up two weeks ago but hasn't tried feature X, schedule a nudge with a quick tutorial.
Re-engagement — user hasn't logged in for 30 days? Schedule a "we miss you" email showing what they've missed.
The insight that changed how I think about this: most SaaS notifications are predictable at the moment the trigger happens. When someone signs up, I know the onboarding sequence. When a trial starts, I know the expiry dates. When a payment fails, I know the follow-up cadence. I don't need a system that continuously polls and evaluates conditions — I just need to schedule the right deliveries at the right time and let them fire.
For the rare cases where circumstances change — user upgrades, cancels, or changes plans — I cancel or reschedule the pending reminders via the API.
Building this in-house is absolutely possible. I've done it. It just takes longer than anyone budgets for, and the maintenance never stops. Provider API changes, timezone rule updates, compliance requirements, the on-call burden when the queue backs up. For most SaaS teams, scheduled notifications aren't what make the product special. They're infrastructure that should just work.
Get started free at pingfyr.com — all channels except email on the free plan, no credit card required.
New channels, API features, and developer guides — straight to your inbox. No spam.