Cron + queue worker

KodMail needs one cron entry (Laravel scheduler) and one queue worker (background jobs). Without these, scheduled campaigns won't send, bounces won't process, and AI usage won't roll up.

Cron — Laravel scheduler

Single cron entry runs schedule:run every minute. Laravel internally dispatches the 11 scheduled tasks at their configured cadence:

* * * * * cd /var/www/kodmail && php artisan schedule:run >> /dev/null 2>&1

What runs on the schedule

CadenceCommandPurpose
Every mincampaigns:dispatchSend queued campaign chunks.
Every mincampaigns:reconcileHeal stuck campaign statuses.
Every minautomations:tickAdvance subscriber enrollments through workflow nodes.
Every minwebhooks:deliverOutbound webhook retry queue.
Every minsystem:heartbeatLiveness signal for the setup-health dashboard.
Every 5 minbounces:processIMAP poll for bounce + complaint messages.
Hourlyai:aggregate-usageRoll up AI token + cost counters.
Daily 03:00send-time:recomputePer-subscriber best-time-to-send re-calc.
Daily 03:15billing:reconcileSelf-heal Stripe vs DB state.
Daily 07:00notifications:scan-billingFire billing.plan_expiring at the configured lead days.
Weekly Mon 08:00digest:businessOwner-only weekly business digest email.

Queue worker

Long-running process that drains jobs table. Required for: large campaign sends, CSV imports, async notification dispatch (when v1.1 lands).

Path A — Supervisor (VPS / dedicated)

; /etc/supervisor/conf.d/kodmail-worker.conf
[program:kodmail-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/kodmail/artisan queue:work --queue=campaigns,imports,default --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=2
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/kodmail-worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start kodmail-worker:*

Path B — systemd service

# /etc/systemd/system/kodmail-worker.service
[Unit]
Description=KodMail queue worker
After=network.target mysql.service

[Service]
User=www-data
Group=www-data
Restart=always
WorkingDirectory=/var/www/kodmail
ExecStart=/usr/bin/php artisan queue:work --queue=campaigns,imports,default --sleep=3 --tries=3 --max-time=3600

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now kodmail-worker

Path C — Shared hosting (no Supervisor)

cPanel installs typically can't run long-lived workers. KodMail's scheduler dispatches a one-off queue:work --once every minute as part of schedule:run. Throughput is limited (~60 jobs/min) but works on any host with a single cron entry.

Local dev

Three terminals:

# Terminal 1 — web server
laragon start    (or `php artisan serve`)

# Terminal 2 — queue worker
php artisan queue:work --queue=campaigns,imports,default

# Terminal 3 — scheduler (replaces cron locally)
php artisan schedule:work

Setup health dashboard

Admin → Settings → System → Setup health shows live tiles:

Green = all good. Yellow = stale heartbeat (cron / worker stopped). Red = error / crash.

Setup health · documentation/screenshots/setup-health.png

/admin/settings/system-setup — live infrastructure tiles + install snippets.