pg_cron handles scheduled SQL. When the job needs to call an API, retry on failure, or run a model, here is what to do instead.
pg_cron runs SQL on a cron schedule inside your database: cleanups, aggregations, function calls. Three things to know before you build on it: schedules are UTC with no conversion UI (9 AM Eastern is 2 PM UTC), RLS policies that check auth.uid() will silently block your cron job, and Edge Function calls via pg_net are fire-and-forget with no retry. For scheduled SQL, pg_cron works. For anything that needs an API call, an email, or a model, the job needs to leave the database.
-- Schedule a daily cleanup at 3am UTC (not your local time) SELECT cron.schedule( 'daily-cleanup', '0 3 * * *', $$DELETE FROM logs WHERE created_at < now() - interval '30 days'$$ ); -- Check what ran SELECT * FROM cron.job_run_details ORDER BY start_time DESC LIMIT 10;
A Task is an AI automation written in Python, hosted on a managed runtime. A developer writes the script; anyone runs it from a form. Integrations, model calls, version control, and logging are built in.
Model and provider agnostic.
When a scheduled job needs to query your database, call a third-party API, send an email, and update a record, that is a script, not a SQL statement. A qbash Task is a Python script that runs on a managed runtime. It connects to your Supabase database directly and calls integrations in code. You set the schedule in your timezone, not UTC. If a run fails partway through, the next attempt picks up from the last checkpoint instead of reprocessing work that already finished. The database call runs server-side outside of Postgres, so the RLS issues that block pg_cron do not apply. Your data stays on Supabase. The script runs against it on a schedule.
users = qbash.integrations.supabase.execute_sql( "SELECT * FROM users " "WHERE trial_ends_at < now() AND reminded = false" ) for user in users: email = qbash.ai.run_prompt( slug="trial-ending-email", variables={"name": user["name"], "plan": user["plan"]}, ) qbash.integrations.gmail.send_email( to=user["email"], subject="Your trial is ending", body=email["body"], ) qbash.integrations.supabase.execute_sql( "UPDATE users SET reminded = true WHERE id = $1", params=[user["id"]], )
When a job reaches past the database, it needs to handle partial failures, query data from multiple sources, and deliver results where the team works.
qbash.parallel processes a list of items and records each one as it finishes. If the run fails at item 40 of 200, the retry starts at item 41. In a pg_cron job, there is no concept of partial progress. A failure means the next scheduled run starts the entire job from the beginning, and any side effects from the first attempt (emails sent, records updated) have already happened.
result = qbash.parallel( send_reminder, users, concurrency=5, max_per_minute=60, checkpoint="trial-reminders", retry=3, )
Two ways to reach your data. The Supabase integration calls the Management API for storage buckets, migrations, and admin operations. The Postgres integration connects directly to the underlying database for parameterized queries and transactions. Credentials are stored on the server and never appear in your script or logs.
# Supabase Management API rows = qbash.integrations.supabase.execute_sql( "SELECT * FROM orders WHERE status = 'pending'" ) # Or connect directly to the underlying Postgres rows = qbash.integrations.postgres.execute_query( "SELECT * FROM orders WHERE status = $1", params=["pending"], )
pg_cron runs inside Postgres, so the job has no way to post to Slack, send an email, or write to a CRM. In a Task, each of those is a function call in the script, called the same way you would call any other Python function.
qbash.integrations.slack.create_message( "#ops", text=f"Reminded {len(users)} users about expiring trials", ) qbash.integrations.gmail.send_email( to="[email protected]", subject="Weekly trial report", body=report["summary"], )
No. Your database stays on Supabase. qbash runs the scheduled job and talks to your database as a data source. pg_cron is still the right tool for scheduled SQL that does not need retries or external calls.
In pg_cron, use the service role key when invoking Edge Functions, or create a database function with SECURITY DEFINER that bypasses RLS. In qbash, the database call is server-side and does not run inside a Postgres session, so RLS policies that check auth.uid() do not apply.
Yes. pg_cron is the right tool for scheduled SQL: cleanups, aggregations, partition maintenance. Move the job to a Task when it needs retries, external API calls, model calls, or runs longer than a database transaction should.
Edge Functions work for short, stateless HTTP handlers. pg_net invocation from pg_cron is fire-and-forget with no retry on failure. A Task handles longer, stateful work with checkpoint-resumable retries and logging.
The cron schedule uses the timezone you set in the task settings. pg_cron uses UTC with no conversion UI, which is the source of most scheduling confusion in the Supabase community.
Competitor details reviewed . Vendors change plans and features without notice, so check theirs before deciding.
Write a script against your Supabase database, set a cron schedule, and the platform handles retries, logging, and deployment.