https://wasp.sh/blog/2025/05/28/how-to-run-cron-jobs-in-postgress-without-extra-infrastructure
Skip to main content
Have a Wasp app in production? We'll send you some swag!
Wasp logoWasp logo
.wasp (beta)Docs
0.16.0
* 0.16.0
* 0.15.0
* 0.14.0
* 0.13.0
* 0.12.0
* 0.11.8
Blog
Search
All our posts
* How to Run CRON Jobs in Postgres Without Extra Infrastructure
* Wasp Launch Week #9 - The road to 1.0 aka Big Thinking Time [?]
* Meet the team - Carlos Precioso
* A Gentle Introduction to Database Migrations in Prisma with
Visuals
* Wasp: The first full-stack framework powered by an LLM - running
on vibes, not a compiler
* Meet the team - Franjo Mindek
* Building Advanced React Forms Using React Hook Form, Zod and
Shadcn
* Wasp Launch Week #8 - it's a Fixer Upper [?]
* Meet the team - Mihovil Ilakovac, Founding Engineer
* ArmadaJS 2024: A Conference That Feels Like Home
* Building React Forms with Ease Using React Hook Form, Zod and
Shadcn
* The Faces Behind Open Source Projects: Pilcrow, author of Lucia
Auth
* Wasp Launch Week #7: Modern Times [?][?]
* Why Your SaaS Emails Aren't Being Delivered and How to Fix This
Issue
* Built in Days, Acquired for $20K: The NuloApp Story
* The Faces Behind Open Source Projects: Tim Jones and pg-boss
* Wasp: The JavaScript Answer to Django for Web Development
* How to Add Auth with Lucia to Your React/Next.js App - A Step by
Step Guide
* Wasp Launch Week #6: The Fun Side of Web Development
* Building and Selling a GPT Wrapper SaaS in 5 Months
* Why We Don't Have a Laravel For JavaScript... Yet
* How to get a Web Dev Job in 2024
* The first framework that lets you visualize your React/NodeJS
app's code
* Open SaaS: our free, open-source SaaS starter
* Wasp Launch Week #5: Waspnado [?]
* On the Importance of RFCs in Programming
* A Guide to Windows Development with Wasp & WSL
* Wasp Launch Week #4: Waspolution
* On the Importance of Naming in Programming
* Contributing to Tech Communities: How Open-Source can land you a
job and get you out of the Skill Paradox
* Build your own AI Meme Generator & learn how to use OpenAI's
function calls
* Using Product Requirement Documents to Generate Better Web Apps
with AI
* Build a real-time voting app with WebSockets, React & TypeScript
[?][?]
* Smol AI vs. Wasp AI - Which is the Better AI Junior
Developer?
* How we built a GPT code agent that generates full-stack web apps
in React & Node.js, explained simply
* GPT Web App Generator - Let AI create a full-stack React &
Node.js codebase based on your description
* Tutorial Jam #1 - Teach Others & Win Prizes!
* Wasp LSP 2.0 - Next-level autocompletion and IDE integration for
Wasp projects!
* What can you build with Wasp?
* Build Your Own Personal Twitter Agent [?] with LangChain
* Wasp Launch Week #3: Magic
* Wasp Beta - May 2023
* Hackathon #2: Results & Review
* Wasp Hackathon #2 - Let's "hack-a-ton"!
* How I Built CoverLetterGPT - SaaS app with the PERN stack, GPT,
Stripe, & Chakra UI
* Wasp steps up its database game with Fully Managed Dev DB & DB
Seeding
* Wasp Auth UI: The first full-stack auth with self-updating forms!
* Wasp Launch Week #2
* New React docs pretend SPAs don't exist anymore
* Building a full-stack app for learning Italian: Supabase vs. Wasp
* 10 "Hard Truths" All Junior Developers Need to Hear
* Wasp Beta - February 2023
* The Most Common Misconceptions Amongst Junior Developers
* From Idea to Paying Customers in 1 Week: An Interview with
Amicus.work
* The Best Web App Framework Doesn't Exist
* Convincing developers to try a new web framework - the effects of
launching beta
* Wasp Beta December 2022
* Hosting Our First Hackathon: Results & Review
* Watch us build a *truly* full-stack app in just 9 minutes w/ Wasp
& ChatGPT
* Wasp Beta brings major IDE improvements
* Feature Release Announcement - Wasp Optimistic Updates
* Permissions (access control) in web apps
* Feature Announcement - TypeScript Support
* Wasp is in Beta: Auth, TypeScript, Tailwind, LSP
* Why we chose Prisma as a database layer for Wasp
* Amicus: See how Erlis built a SaaS for legal teams with Wasp and
got first paying customers!
* How Michael Curry chose Wasp to build Grabbit: an internal tool
for managing dev resources at StudentBeans
* Wasp Beta Launch Week announcement
* How Wasp reached all-time high PR count during Hacktoberfest:
tips for OSS maintainers
* Alpha Testing Program: post-mortem
* Feature Announcement - Tailwind CSS support
* Feature Announcement - New auth method (Google)
* Farnance: How Julian built a SaaS for farmers with Wasp and won a
hackathon!
* How Wasp reached 1,000 stars on GitHub (detailed stats &
timeline)
* Building an app to find an excuse for our sloppy work
* How to get started with Haskell in 2022 (the straightforward way)
* How and why I got started with Haskell
* How to communicate why your startup is worth joining
* ML code generation vs. coding by hand - what we think programming
is going to look like
* Feature Announcement - Wasp Jobs
* How to win a hackathon. Brief manual.
* Meet the team - Filip Sodic, Founding Engineer
* Build a metrics dashboard with background jobs in Wasp - Say
hello to Waspleau!
* Meet the team - Shayne Czyzewski, Founding Engineer
* How we built a Trello clone with Wasp - Waspello!
* Our fundraising learnings - 250+ meetings in 98 days to the
oversubscribed round
* Following YC, Wasp raised $1.5M Seed Round led by Lunar Ventures
and HV Capital
* Tutorial: `forall` in Haskell
* How to implement a Discord bot (in NodeJS) that requires new
members to introduce themselves
* Wasp - language for developing full-stack Javascript web apps
with no boilerplate
* Journey to YCombinator
* Hello Wasp!
How to Run CRON Jobs in Postgres Without Extra Infrastructure
May 28, 2025 * 9 min read
Andrei Gaspar
Andrei Gaspar
Content contributor
I had the pleasure to spend nearly 3 years working on live video
infrastructure at TV2 Norway. As you know, when it comes to
infrastructure, time IS money. Everything needs to be just-in-time,
especially when it comes to provisioning costly components like
encoders. For example, if there's a major live sports match
scheduled, you want to ensure the streaming infrastructure is set up
just minutes before kickoff, and torn down shortly after the final
whistle blows.
The fact is, scheduling is synonymous with infrastructure complexity.
But sometimes you're just working on your trusty little Node app, and
all you really need is something simple -- like sending a daily
reminder to users about today's coding challenge. That's exactly the
kind of lightweight scheduling we'll dive into in this article.
Scheduling Jobs with Wasp and PgBoss
What is PgBoss?
PgBoss is a job queue built on PostgreSQL. It leverages the
database's reliability, transactional safety, and scalability to
manage background jobs efficiently. Unlike Redis-based queues, PgBoss
doesn't require additional infrastructure -- just Postgres, which is a
great fit if you're already using it as your database.
Like a boss
Key features:
* Transactional safety - jobs are stored in Postgres, ensuring they
survive crashes.
* Retries & timeouts - failed jobs can be automatically retried.
* Scheduling - supports both delayed and recurring (CRON) jobs.
* No extra infrastructure - only Postgres needed.
Why Wasp Makes It Even Better
Wasp is a full-stack framework for React & Node.js that simplifies
web app development by handling boilerplate for you. When combined
with PgBoss, Wasp provides:
* Declarative job definitions - define jobs directly in your Wasp
config.
* Everything just works - no need to manually set up workers or
queues.
* Type safety - jobs are type-checked, reducing runtime errors.
NOTE: Since PgBoss runs in the same process as your Wasp app,
CPU-heavy jobs can impact API responsiveness. For high-load
scenarios, consider offloading to a dedicated worker process.
When Should You Consider a Different Solution?
For most side projects and early-stage startups, this setup will work
perfectly fine. As a rule of thumb, if you're processing less than
1000 jobs per day or your jobs are mostly lightweight operations
(like sending emails or updating records), you can stick with this
solution.
However, you might want to consider a dedicated job processing system
when:
* Your jobs take more than a few seconds to complete.
* You're processing thousands of jobs per day.
* Your jobs involve heavy computational tasks (like image
processing or data analysis).
* You need to scale job processing independently from your main
application
A dedicated system means running your jobs on a separate server or
process, isolated from your main application. This prevents
long-running jobs from affecting your app's performance.
But remember: premature optimization is the root of all evil. Start
with this simple solution, and only upgrade when you have concrete
evidence that you need something more robust.
We'll look at two use cases:
1. One-time scheduled jobs (e.g., send a reminder email at a
specific time).
2. Recurring (CRON) jobs (e.g., daily digest emails).
We are going to use the Wasp Tennis Score example app to demonstrate
the functionality.
The main interface of our tennis score tracking appThe main interface
of our tennis score tracking app
This is a neat little tennis score tracking app we've built. Think of
it like this: you're running a tennis tournament and need to keep
track of who's winning. The judges can punch in scores as the matches
happen, and everyone watching can see the results.
To run the app, you'll need to clone the repo, create a new
.env.server file (copy example.env.server as a starting point),
populate it with the required values and execute the sequence of
commands listed in the README file here.
Requirements:
* Node.js >=20 installed -- I recommend using nvm.
* A Postgres database connection URL -- but in our case for
development you can just use wasp db start.
* Google OAuth credentials (Client ID and Client Secret)
* Mailgun config (API Key, Domain, API Url)
How to Create Scheduled Jobs with Wasp
After cloning the repository, you can check out the working example
implementation of the scheduled job feature by running the following
command:
git checkout scheduled_job_added
Start the app with wasp start and authenticate with Google.
You'll be met with a UI that displays a list of tennis matches.
Ongoing (i.e. Live) matches on the top and Completed Matches below.
In the header you will find a "Schedule Summary Email" button. That
button is configured to send you a summary of the previous day's
matches to your email.
The logged-in interface of our tennis scoring appThe logged-in
interface of our tennis scoring app
Click the button and check the email address linked to your Google
account. You should see something similar to the screenshot below.
Email summary screenshotEmail summary screenshot
Since this is a new account without any record of previous day's
matches, the summary will be empty.
By default, the feature sends the email immediately for easier
testing and development. I'll walk you through the implementation
below.
Two resources in the main.wasp file do the heavy lifting for us: an
action and a job.
// main.wasp
action scheduleEmailSummary {
fn: import { scheduleSummaryEmail } from "@src/matches/operations",
entities: [Match]
}
job sendEmailSummaryJob {
executor: PgBoss,
perform: {
fn: import { sendEmailSummary } from "@src/workers/schedule",
},
entities: [Match]
}
The sendEmailSummary job retrieves matches, converts the results into
a human-readable format, and delivers the email through Mailgun.
// src/workers/schedule.ts
export const sendEmailSummary: SendEmailSummaryJob = async (
{ email },
context
) => {
// Find yesterday's completed matches
const matches = await context.entities.Match.findMany({
// ...
// Query
})
// Generate summary
const { textContent, htmlContent } = generateMatchSummary(matches)
// Send Summary
const summary = await emailSender.send({
// ...
// Config
text: textContent,
html: htmlContent,
})
}
The scheduleEmailSummary action on the other hand controls when the
code associated with the job should be executed
// src/matches/operations.ts
export const scheduleEmailSummary = async (_, context) => {
// ...
const { email } = context.user
// ...
// TODO: Update this date with the value you need (for example, tomorrow morning)
const sendAt = new Date().toISOString()
await sendEmailSummaryJob.delay(sendAt).submit({
email,
})
}
As you can see above, the sendAt variable determines the job's
execution time. Just provide a date to the delay() function, and the
job will execute at that specified time.
This is the meat and potatoes of the functionality, the only thing
left is to decide when are you going to run the scheduleEmailSummary
action.
I chose to simply hook it up to a button click listener, but you can
let your imagination run wild and call it from wherever you want in
the code.
You can find the current implementation in the IndexPage.tsx file.
// src/matches/IndexPage.tsx
import { scheduleEmailSummary } from 'wasp/client/operations'
// ...
const handleScheduleSummaryEmailClick = () => {
// Call the action
scheduleEmailSummary()
.then(() => {
// ...
})
.catch((error) => {
// ...
})
.finally(() => {
// ...
})
}
// ...