Scheduled messaging, durable by default.
A BullMQ + Redis job queue that powers every reminder, campaign, abandoned-cart nudge, and drip sequence on the StoreTalk platform. Multi-tenant, observable, SDK-driven — available to first-party and third-party apps alike.
installId: tenantId,
customerId: 'cust_8f...',
message: 'Reminder: appt at 11 AM',
fireAt: new Date('2026-04-29T05:00:00Z'),
refId: 'booking_b7d2'
});
// → { jobId: "j_3k9...", status: "queued" }
Pending jobs
142 total
Booking reminder
booking_b7d2 · in 4h 23m
Abandoned cart
cart_a12 · in 1h 47m
Renewal reminder
sub_99k · in 2 days
For
Infrastructure that every conversational app needs
Conversational commerce runs on time-delayed messages. Every app would otherwise build the same Redis-backed scheduler again. We built it once.
Schedule a message for any future moment
Booking reminder 24 hours before appointment. Abandoned-cart nudge 2 hours after browsing. Renewal alert 7 days before subscription expiry. The Scheduler holds delayed jobs durably in Redis and fires them at the precise moment, even if the app server restarts in between.
- Schedule by absolute timestamp or relative delay
- Multi-tenant isolation — each tenant's jobs in a separate queue
- Sub-minute precision for time-critical alerts
- Time-zone aware — fires in tenant's local TZ, not UTC
- Up to 90-day delay supported out of the box
Cron patterns for recurring sends
Weekly subscription renewal reminder, daily fitness class summary, monthly invoice for B2B customers, quarterly health-check nudge. Standard cron syntax, with named templates for common patterns ("every Monday 9 AM IST", "first of every month").
- Standard cron syntax — minute, hour, day, month, weekday
- Named patterns for non-developer users
- Per-recurring-job customer-list update
- Pause and resume without losing the schedule
- Bounded by tenant subscription limits
Cancel by reference ID — clean state
A customer pays before the abandoned-cart reminder fires. Their booking is rescheduled before the reminder goes out. Each scheduled job has a reference ID (typically the booking ID, order ID, or campaign ID); cancel pending jobs by reference and the system cleans up gracefully.
- Cancel a single job by reference ID
- Bulk cancel — all jobs matching a tenant + ref pattern
- Cancel-and-replace pattern for stateful workflows
- Idempotent cancellation — safe to call repeatedly
- Audit log of every cancellation for support traceability
HMAC-signed callbacks for every fire
When a scheduled job fires, the Scheduler doesn't send the WhatsApp message itself — it calls back to StoreTalk Core with an HMAC-signed payload. Core then resolves the customer's active channel (WhatsApp, web, voice) and dispatches the message. Same security model as inbound webhooks.
- HMAC-SHA256 signature on every callback request
- Replay protection — timestamps with skew tolerance
- Retry with exponential backoff on callback failure
- Dead-letter queue for jobs that fail repeatedly
- Configurable per-job callback URL (advanced use cases)
Powers every StoreTalk app — and yours
E-commerce uses it for abandoned-cart reminders. BookingPro for appointment reminders. Campaign Manager for batch sends. Loyalty programmes for renewal nudges. Third-party apps installed via the StoreTalk SDK get the same scheduler with zero extra setup — just call app.scheduleNotify().
- Single-line SDK call — no infra to provision
- Per-app job namespace — your jobs, your IDs
- Cross-app job inspection forbidden — strict isolation
- Documented in the public SDK reference
- Free for tenant-routed jobs; metered for high-volume apps
Observable — see every pending job
Operations transparency matters. The Scheduler dashboard shows pending jobs per tenant, fire-time histograms, success/failure rates per app, and a live activity feed. Debug a "why didn't this reminder fire?" question without opening Redis.
- Per-tenant pending jobs view
- Fire-time histogram — see scheduling load by hour
- Per-app success / failure / retry stats
- Live activity feed for the last 1,000 fires
- Prometheus metrics endpoint for your monitoring stack
SDK call → durable queue → callback to Core
Your app calls scheduleNotify() via the SDK
One line: app.scheduleNotify({ installId, customerId, message, fireAt, refId }). The SDK signs the request, posts to scheduler.storetalk.app, and gets back a job ID.
Scheduler enqueues the job in Redis durably
The job lands in BullMQ's Redis-backed queue. Survives Redis restart (RDB + AOF persistence), survives Scheduler restart. Held until fireAt.
At fireAt, Scheduler hits Core, Core dispatches
The Scheduler doesn't send WhatsApp directly — it calls Core's /api/app-events/scheduled-fire endpoint. Core looks up the customer's active channel and sends the message there.
Used by every StoreTalk app
Booking reminders
24h + 1h before appointment
Abandoned-cart recovery
2h after cart abandonment, with discount
Subscription renewal
7d before renewal, again 1d before
Loyalty programme nudges
Monthly redemption reminders
Campaign batch sends
Schedule a marketing blast for 9 AM tomorrow
Drip sequences
Day-1, Day-3, Day-7 onboarding messages
Drop-off recovery
Reminder if a flow stalled mid-conversation
Compliance reports
Monthly auto-export to enterprise tenants
Frequently asked
Is the Scheduler something tenants interact with directly?
Why a separate service instead of in-process queues?
Where does it run?
What happens if the callback fails?
Can I see what's scheduled for my tenant?
How does it scale?
Is there a public API or only the SDK?
What does it cost a tenant?
Build apps on the same scheduler we use.
Read the SDK docs to start scheduling delayed and recurring messages from your own StoreTalk app.