Skip to content

Background Jobs & Cron

What this module does

Some operations are too slow or too broad to run inside a web request:

  • Syncing thousands of emails across all cases
  • Running overnight reports across all 40+ agencies
  • Sending scheduled notifications

Two separate systems handle this work: the Huey job queue (orchid_jobs) for on-demand async tasks, and cronapp.py for scheduled jobs.

Key files

  • cronapp.py — 42 KB scheduled job runner
  • Directoryorchid_jobs/
    • __init__.py — app factory, Huey configuration
    • Directoryapp/
      • app.py — Huey setup, Flask app context
    • Directoryjobs/
      • email_sync.py — email sync jobs (periodic + on-demand)
      • email_matching.py — email-to-contact matching job
      • example.py — reference implementations
    • Directorymodels/
      • job.py — Job tracking model
      • error.py — Job error logging
    • Directoryhelpers/
      • email.py — email helpers for jobs

The two background systems

orchid_jobs — Huey task queue

An async task queue built on Huey. It runs as a separate process from flaskapp.py.

How it works:

  1. flaskapp.py enqueues a task by calling a Huey task function
  2. The task is stored in the queue backend (memory, SQLite, or Redis depending on environment)
  3. A Huey worker process picks up the task and executes it
  4. The worker runs inside its own Flask app context (from orchid_jobs/__init__.py)

Available jobs:

JobWhen it runsWhat it does
EMAIL_SYNC_PERIODICPeriodicallySyncs email for all agencies
EMAIL_SYNC_AGENCYOn-demandSyncs email for one specific agency
EMAIL_SYNC_ADMINOn-demandSyncs email for one specific admin
EMAIL_SYNC_DISCONNECTOn-demandDisconnects email sync for an admin
EMAIL_MATCHINGAfter syncMatches emails to cases/contacts

Huey backends by environment:

  • MemoryHuey — local development (tasks lost on restart)
  • SqliteHuey — staging (tasks persisted in SQLite)
  • RedisHuey — production (tasks persisted in Redis)
  • BlackHoleHuey — testing (tasks discarded immediately)

cronapp.py — the scheduled runner

A separate Flask application that runs on a cron schedule (configured in AWS). Unlike flaskapp.py, which connects to one agency’s database, cronapp.py iterates all agency databases.

How it works:

# Pseudocode of cronapp.py's main pattern:
for db_name in get_all_agency_databases():
engine = create_dynamic_engine(db_name)
with app.app_context():
# run job for this agency
run_notifications(engine)
run_health_checks(engine)
# etc.

What cron jobs do:

  • Send scheduled notifications (reminders, upcoming events)
  • Run health checks on agency data integrity
  • Process overnight aggregations
  • Clean up expired sessions and tokens

Job context — carrying Flask state into background jobs

Background jobs run outside the normal request lifecycle, so they cannot access g or session. The JMSJobContext class in orchid_jobs is used to carry necessary context (agency ID, admin ID, etc.) into job functions:

# Enqueue a job from a web request:
context = JMSJobContext.from_request()
email_sync_job(context.serialize())
# Inside the job:
def email_sync_job(context_data):
context = JMSJobContext.deserialize(context_data)
with app.app_context():
# use context.agency_id, context.admin_id, etc.

Gotchas