Reference
Worker and scheduler reference
Generic host and Railway queue/reconciliation configuration.
Includes the complete docs/QUEUE_WORKER.md reference from this build. Application paths refer to your own installation.
On this page
Queue worker and billing reconciliation#
Watched-discussion email uses Laravel's database queue. Run a worker against the same database as the web application. Verification and password-reset messages still send directly through the configured mail transport. Each reply queues a single fan-out job; the worker uses it to queue one mail job per email-enabled watcher, 100 at a time, so email for a new reply starts only once a worker has picked that job up. In-app notifications do not need the worker.
Railway#
The worker service follows joogiebear/voltexahub on main, with repository root /. It builds the same Dockerfile as web, then runs sh deploy/worker.sh. It needs no public domain, HTTP health check, or upload volume. Keep it awake with one replica.
Railway no longer accepts new Config as Code file paths. Settings are configured directly on each service; the retired root railway.json has been removed so the worker does not inherit the web health check. Preserve these settings when recreating services:
| Setting | web |
worker |
|---|---|---|
| Source | joogiebear/voltexahub, branch main |
Same |
| Builder / Dockerfile | Dockerfile / Dockerfile |
Same |
| Start command | Docker default (sh deploy/start.sh) |
sh deploy/worker.sh |
| Pre-deploy command | php artisan migrate --force |
None; web runs migrations |
| Health check | /up, 120 seconds |
None |
| Restart policy | On failure, 3 retries | Always |
| Shutdown grace period | Existing default | 70 seconds |
| Replicas / serverless | 1 / disabled | 1 / disabled |
On the first rollout, wait for the web migration to complete before starting the worker. Subsequent worker changes must remain compatible with the previous schema until the web deployment migrates it. Never configure an HTTP health check on the worker.
Reference these variables from web using Railway's ${{web.VARIABLE_NAME}} syntax so both services use the same values:
APP_NAME, APP_ENV, APP_DEBUG, APP_KEY, APP_URL, DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD, CACHE_STORE, QUEUE_CONNECTION, MAIL_MAILER, MAIL_FROM_ADDRESS, MAIL_FROM_NAME, RESEND_API_KEY, BILLING_ENABLED, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID, PAYPAL_MODE.
Set LOG_CHANNEL=stderr. Use QUEUE_CONNECTION=database in both services and keep the default queue name default. If using a mail transport other than Resend, also share that transport's variables. The standard database retry interval is 90 seconds, longer than the worker's 60-second timeout; retain that ordering if changing either value. The Docker image includes pcntl for timeouts and graceful termination.
The worker caches configuration and views at startup. Railway restarts it after exits, including its hourly refresh or queue:restart; deployments provide a 70-second termination grace period. The web service owns migrations. Verify that both web and worker deploy the intended Git commit after each push.
Other hosts#
Use a process supervisor to run and automatically restart:
php artisan queue:work database --queue=default --sleep=3 --tries=3 --backoff=60 --timeout=60 --max-time=3600 --memory=128
Run migrations before starting the worker. Run php artisan queue:restart after releasing new application code, and allow at least 70 seconds for graceful shutdown. The PHP CLI needs pcntl for timeouts on Linux.
Delivery checks and recovery#
Inspect worker logs and php artisan queue:failed. Fix the provider or configuration problem before selectively retrying failed job IDs with php artisan queue:retry <id>. Job handlers recheck current subscription and forum access when retried. Queue delivery is at least once: a process failure after the provider accepts a message can cause a duplicate on retry.
For an end-to-end check, opt a verified test account into a watched discussion, post a reply as a second account, confirm the worker completes the job and the intended inbox receives the email, then verify its discussion and unsubscribe links. Repeat after turning email off to confirm no new delivery. Never use the production database for automated tests or send test mail to addresses you do not control.
Billing reconciliation#
deploy/worker.sh supervises both queue processing and deploy/billing-scheduler.sh. The latter runs php artisan billing:enqueue each minute; a source is eligible every 15 minutes, with a fair limit of 100 configured-provider sources per batch. This command checks the migrated billing schema before querying it, so a worker deployed before web migration remains compatible. A failed scheduler or queue process terminates the companion and exits for supervisor restart. TERM/INT propagate to both children and the scheduler's current command/sleep, then wait for shutdown. Use the existing 70-second grace period and keep the queue timeout60 below retry_after90.
Billing jobs need shared database cache (CACHE_STORE=database) across web and worker for pending-job uniqueness, and all provider key references above. They still serialize canonical fetch/application under current database row locks. On other hosts, supervise queue processing and sh deploy/billing-scheduler.sh together, or run php artisan billing:enqueue every minute in the host scheduler. schedule:work is not separately required. Provider HTTP calls are bounded without automatic retries; the queue handles backoff/retries. Do not mistake a running scheduler or HTTP contract test for a completed real provider payment. See billing setup and recovery.