Quickstart: run a Python background job with PostgreSQL¶
Soniq is a Python background job library that uses PostgreSQL as its queue. This page takes you from pip install to a running worker in about 5 minutes.
1. Install¶
You need a running PostgreSQL. If you do not have one, start one with Docker:
To try Soniq without PostgreSQL, run
pip install "soniq[sqlite]". Then pass a file path instead of a URL:Soniq(database_url="dev.db"). SQLite and the in-memory backend (Soniq(backend="memory")) are for local development and tests only. See the testing guide and the Backend glossary entry.
2. Set the environment¶
The soniq command and your Python code both read the database URL from the environment. Set both variables in your shell:
SONIQ_JOBS_MODULES lists the Python modules that the worker imports on startup. The import runs the @soniq.job() decorators, which register your jobs. Without it, the worker does not know which jobs exist.
3. Define a job¶
Create a file named jobs.py:
# jobs.py
import asyncio
from soniq import Soniq
soniq = Soniq()
@soniq.job(name="emails.send_welcome")
async def send_welcome(to: str):
print(f"Sending welcome email to {to}")
if __name__ == "__main__":
async def main():
await soniq.enqueue(send_welcome, to="dev@example.com")
await soniq.close()
asyncio.run(main())
The job has an explicit name. When you run python jobs.py, Python calls the file __main__. Without a fixed name, Soniq stores the job as __main__.send_welcome. The worker imports the file as jobs, so it cannot find that name.
4. Set up the database¶
This command creates the tables that Soniq needs. It is idempotent: you can run it more than once and it changes nothing the second time.
5. Start a worker¶
The worker process must be able to import your job code. For cross-service setups and per-worker overrides, see Job module discovery.
6. Enqueue a job¶
Open another terminal and run:
The worker prints "Sending welcome email to dev@example.com". You now have a working background queue.
What changes in production¶
Make these changes before you deploy.
- Read settings from the environment. Do not hardcode
database_url. Soniq readsSONIQ_DATABASE_URLautomatically. - Set
SONIQ_JOBS_MODULESso workers import your job functions on startup. - Run
soniq setuponce per deploy. Do not run it from every replica. See going to production. - Make handlers idempotent. An idempotent handler gives the same end result when it runs more than once. Soniq guarantees at-least-once delivery. If a worker crashes after it runs your function but before it marks the job done, another worker runs the job again. To make a rerun harmless, use
INSERT ... ON CONFLICT DO UPDATEinstead of a plain insert. You can also check whether you already sent the email, or store an idempotency token with the side effect. - Tune timeouts. Every job has a default timeout of 300 seconds. Override it per job:
@soniq.job(name="reports.build", timeout=600).
New to job queues? The tutorial explains each term in depth. You do not need to learn all of this now.
Next steps¶
To learn each concept, work through the tutorial. It has six chapters and takes about 30 minutes.
If you already know job queues, go to one of these pages:
- FastAPI guide - the most common producer setup
- Going to production - the checklist for a healthy deploy
- Migrating from Celery or from RQ
Working with an AI coding agent? Point it at AGENTS.md and llms.txt. They cover the canonical patterns and the mistakes agents make most often.