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(() => { // ... }) } // ...