Celery tasks
Celery (EMAW_BACKEND/celery.py, app name emaw) autodiscovers tasks.py in every app. Broker and results are Redis databases /1 and /2 of REDIS_URL. Tasks ignore results, ack late, prefetch one, and have a 300 s hard / 240 s soft time limit. The request id is propagated into task logs.
Two compose services run it: worker (--concurrency=2) and beat (schedule file in /tmp, so it is rebuilt on restart). In tests Celery is eager: tasks run inline.
Beat schedule (CELERY_BEAT_SCHEDULE in settings/base.py)
| Name | Task | Every |
|---|---|---|
events-fetch-meet-attendance | events.tasks.fetch_recent_meet_attendance | 30 min |
deposco-sync-orders | deposco.tasks.sync_deposco_orders | DEPOSCO_SYNC_INTERVAL_MINUTES (default 30) |
deposco-sync-inventory | deposco.tasks.sync_deposco_inventory | DEPOSCO_INVENTORY_SYNC_MINUTES (default 60) |
deposco-sync-order-history | deposco.tasks.sync_deposco_order_history | 60 min |
Deposco has no webhooks, so shipments, tracking, inventory and all-channel order history are polled.
Tasks by module
| Module | Tasks | Triggered by |
|---|---|---|
store/tasks.py | send_order_confirmation_email, send_order_shipped_email, link_order_to_contact, send_order_staff_notification | Stripe webhook (paid), Deposco sync (shipped) |
events/tasks.py | geocode_event, link_registration_to_contact, send_event_confirmation_email, send_guest_confirmation_email, send_overbooked_staff_notification, sync_event_meeting, send_leader_email, fetch_event_attendance, fetch_recent_meet_attendance | Event save, webhook, staff actions, beat |
donations/tasks.py | link_donation_to_contact, send_donation_receipt_email, send_donation_staff_notification, send_donation_payment_failed_notification, send_recurring_donation_cancelled_emails | Stripe webhook (checkout, invoice, subscription events) |
deposco/tasks.py | submit_order_to_deposco, sync_deposco_orders, sync_deposco_inventory, sync_deposco_order_history, notify_deposco_problem | Webhook (paid), beat, staff resubmit |
crm/tasks.py | geocode_contact, run_geography_assign | Contact save / address change, bulk assignment runs (GeographyAssignRun, polled by the CRM) |
campaigns/tasks.py | start_campaign, send_campaign_batch, finalize_campaign, generate_ai_template_draft | Send/schedule, batch chaining, AI draft requests |
The commerce flow in tasks
Linking a buyer to the CRM replaces the contact's address with the billing address, geocodes it (geocode_contact) and re-assigns region and areas. Nothing is linked before payment.
Campaign send pipeline
start_campaign builds the recipient list from the audience definition (respecting suppression, marketing status and the CAMPAIGN_SEND_ALLOWED_GROUP_IDS guard), then chains send_campaign_batch jobs sized by CAMPAIGN_BATCH_SIZE at CAMPAIGN_SEND_RATE_PER_MINUTE, and finalize_campaign closes it. Delivery and engagement events come back later through the ACS Event Grid webhook. Details: campaigns/README.md.
Operating
docker compose logs -f worker
docker compose logs -f beat
docker compose exec django python -m celery -A EMAW_BACKEND inspect active
docker compose exec django python -m celery -A EMAW_BACKEND inspect scheduled
If Redis is restarted, queued tasks are lost (redis_data persists on disk but Celery queues are not the kind of thing to trust after a crash). Re-trigger from the admin app (resubmit order, resend confirmation) or management commands (deposco_submit_order, deposco_sync).