Skip to content
Pages

Platform

Scheduled jobs

The jobs that must run on a schedule (store syncs, the CRM mailbox read, the product pipeline backfill and the backups), how each is triggered in production, how it is authorised, and where failures show up.

Updated 23 October 2026View as Markdown

A few things in Leitagu only happen when something calls them on a schedule: store connections sync, the CRM reads its mailbox, and the product pipeline backfill brings every product up to date. The apps have no scheduler of their own (no timers or cron libraries in the code), so each job is an HTTP endpoint that an outside scheduler calls. In production that scheduler is Coolify's Scheduled Tasks, which run a curl command inside the app's own container. This page lists each job as of 2026-10-23, including one that is not scheduled yet.

Summary

Job Endpoint App Schedule in production Status
Store sync POST /api/cron/sync Partner Coolify task partner-hourly-sync, 5 * * * * (every hour at :05) Running
CRM mailbox read POST /api/cron/mail CRM Coolify task crm-mail-sync, */5 * * * * (every 5 minutes) Running
Product pipeline backfill POST /api/cron/pipeline Partner None Gap: not scheduled
Database backups (Coolify, no endpoint) leitagu-postgres Every 6 hours, and Coolify's own backup daily at 02:30 Running

Times are Estonian time (Europe/Tallinn), which is Coolify's instance time zone.

How the jobs are triggered

  • Coolify Scheduled Tasks. Each task belongs to one app in Coolify (the app's Scheduled Tasks tab) and runs its command in that app's running container, so it calls the app on its own port at 127.0.0.1 and never goes over the internet.
  • Auth. Every /api/cron/* endpoint needs the header Authorization: Bearer followed by CRON_SECRET, compared in constant time; anything else gets 401. The task reads $CRON_SECRET from the container's environment, so the value is never written in the command. CRON_SECRET is set on the partner and CRM apps, and must be the same value on both.
  • GET also works on every cron endpoint, for schedulers that can only make GET requests.
  • Failures. The commands use curl -fsS, so an error status (401, 409, 500) or a timeout fails the task. Each run's status, duration and the endpoint's JSON answer are in the app's Scheduled Tasks tab in Coolify. No notification channel is set up in Coolify yet, so a failing task is only seen there (see Known gaps).
  • Other hosting. deploy/vm/leitagu-sync.cron has the same three calls as a cron file for the one-server setup in DEPLOYMENT.md. It is not used in production. The droplet's own crontab and systemd timers run nothing for Leitagu.

Store sync

  • Endpoint. POST /api/cron/sync on the partner app (apps/partner/src/app/api/cron/sync/route.ts).

  • What it does. Runs every enabled Shopify, WooCommerce and feed connection that is due, one after another. Each connection's own schedule (hourly, daily or manual) decides whether it is due, so the endpoint must be called every hour. Synced products then go through the product pipeline. Answers {"ran": <count>, "results": [{"id", "status"}]}.

  • Production. Coolify task partner-hourly-sync on the partner app, 5 * * * *, timeout 1800 seconds:

    curl -fsS -m 1800 -X POST -H "Authorization: Bearer $CRON_SECRET" http://127.0.0.1:3002/api/cron/sync
    

    Scheduled since 2026-10-18. Last run before this page was written: 2026-10-23 00:05, succeeded in about 3.5 minutes; it synced three connections, all with the result partial.

  • Failures. A connection that fails doesn't stop the others; the endpoint still answers 200 and lists its status. Each run is recorded in sync_runs with its errors and issues, shown on the connection in the partner app and on the store in the admin app. Connections that stop syncing show up as out of date in admin (see Out-of-date data).

CRM mailbox read

  • Endpoint. POST /api/cron/mail on the CRM (apps/crm/src/app/api/cron/mail/route.ts).

  • What it does. Copies new emails from the shared mailbox's inbox and Sent folder into the CRM over IMAP (see Migadu). Answers {"stored": <count>, "folders": [...]}; 409 not_connected when the mailbox settings are missing.

  • Production. Coolify task crm-mail-sync on the CRM app, */5 * * * *, timeout 150 seconds:

    curl -fsS -m 120 -X POST -H "Authorization: Bearer $CRON_SECRET" http://127.0.0.1:3003/api/cron/mail
    

    Scheduled since 2026-10-22. Runs take about 2 seconds; the last run before this page was written, at 2026-10-23 00:25, succeeded.

  • Failures. A folder that can't be read keeps its error in crm_mail_state, shown in the CRM's Mailbox next to "Last checked"; the endpoint still answers 200, so the Coolify task stays green. "Check for new email" in the CRM reads the mailbox at once.

Product pipeline backfill

  • Endpoint. POST /api/cron/pipeline on the partner app (apps/partner/src/app/api/cron/pipeline/route.ts). Options: ?seconds= (time budget, default 240), ?after=<next> to continue, ?force=<stages> or ?force=all, ?limit=.

  • What it does. Runs the product pipeline stages that are missing, out of date or of an older version, for every product, and answers with counts and next (null when it got through everything). npm run pipeline:backfill calls it in a loop.

  • Expected schedule. Daily, and after every deploy (deploy/vm/leitagu-sync.cron uses 03:35 with ?seconds=1500).

  • Production: not scheduled (gap). There is no Coolify task for it, and the images deploy workflow doesn't call it after a deploy. Products still go through the pipeline when they are synced, created, edited or corrected, so day to day the gap shows only when a stage changes (a new stage or a version bump) or when a product's pipeline run failed: those products wait until someone runs the backfill by hand.

  • To add it. A Coolify task on the partner app, for example 35 3 * * * with a timeout of at least 1800 seconds:

    curl -fsS -m 1800 -X POST -H "Authorization: Bearer $CRON_SECRET" 'http://127.0.0.1:3002/api/cron/pipeline?seconds=1500'
    

    One call stops after its time budget; if next isn't null, the next day's run continues from the start, skipping what is already up to date.

  • Failures. Products whose stages fail are counted as failed in the answer; products that need a person go to the review queue in the admin app.

Backups

Coolify also runs the database backups on a schedule: leitagu-postgres every 6 hours (00:00, 06:00, 12:00, 18:00) and Coolify's own database daily at 02:30, both uploaded to Cloudflare R2. They show under the database's Backups tab. See Backups and restore.