Skip to main content

Supabase cron jobs.

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.

What pg_cron handles.

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.

cron.sql
-- 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;

    qbash Tasks.

    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.

    • Anthropic
    • OpenAI
    • Gemini
    • xAI
    • OpenRouter
    • Amazon
    • Perplexity
    • MoonshotAI
    • Meta
    • Qwen

    Writing the job as a Python script.

    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.

      remind.py
      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"]],
          )

      Retry, database access, and delivery.

      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.

      Checkpoints and retry

      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.

      backfill.py
      result = qbash.parallel(
          send_reminder, users,
          concurrency=5, max_per_minute=60,
          checkpoint="trial-reminders", retry=3,
      )

      Direct database access

      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.

      query.py
      # 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"],
      )

      Delivery in one line

      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.

      notify.py
      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"],
      )

      Questions

      Does this replace Supabase?

      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.

      How do I handle RLS with pg_cron?

      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.

      Can I still use pg_cron for simple jobs?

      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.

      What about Edge Functions on a schedule?

      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.

      How does timezone handling work in qbash?

      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.

      Ship your first task.

      Write a script against your Supabase database, set a cron schedule, and the platform handles retries, logging, and deployment.