Scheduled Syncs¶
Automate data syncs with cron schedules so your warehouse stays fresh without manual intervention.
Prerequisites
- At least one data source configured (
dango source add) dango startrunning (local) or cloud deployment active
Web UI
You can view schedules and trigger runs from the Schedules page in the Web UI at http://localhost:8800/schedules. See execution history, next run times, and manually trigger schedules. Schedule configuration is CLI-only. See Web UI — Schedules.
Quick Start¶
-
Add a schedule:
-
Verify it's registered:
-
Check the next run time:
That's it — your sources will sync automatically on the cron schedule you chose.
Detailed Steps¶
The Schedule Wizard (dango schedule add)¶
The interactive wizard walks you through creating a schedule:
$ dango schedule add
📅 Schedule Setup
─────────────────
Schedule name (lowercase, letters/numbers/underscores): daily_sync
Schedule type:
Sync & Transform (recommended)
Sync only (no transform)
Transform only (dbt)
Choice: Sync & Transform (recommended)
Select sources to sync:
[x] stripe
[x] google_sheets
[x] hubspot
(SPACE to toggle, ENTER to confirm)
Run frequency:
Every hour
Every 2 hours
Every 3 hours
Every 4 hours
Every 6 hours
Every 8 hours
Every 12 hours
Daily
Weekly
Custom cron
Choice: Daily
Timezone (leave blank for UTC): America/New_York
✅ Schedule 'daily_sync' created
Next run: 2026-05-16 06:00:00 EDT
Schedule Types¶
| Type | What It Does | When to Use |
|---|---|---|
sync | Syncs selected sources, then runs dbt run + dbt test | Recommended for most workflows. Keeps models up to date after every sync. |
sync_only | Syncs selected sources only — skips dbt entirely | When you want to decouple ingestion from transformation, or run dbt on a separate schedule. |
dbt | Runs a dbt command only — no source sync | For dbt-only schedules (e.g., hourly dbt run on models that depend on external tables). |
When to use sync_only
If you have 10 sources syncing hourly, each sync schedule runs a full dbt run after every source sync — that's 10 dbt runs per hour. Use sync_only for individual sources and a separate dbt schedule to run transformations once.
Frequency Options¶
The wizard offers these frequency presets, then prompts for specific time details (hour, minute, day of week):
| Preset | Description |
|---|---|
| Every hour | Standard hourly sync |
| Every 2 hours | Twice-daily-ish |
| Every 3 hours | 8 times daily |
| Every 4 hours | 6 times daily |
| Every 6 hours | 4 times daily |
| Every 8 hours | 3 times daily |
| Every 12 hours | Twice daily |
| Daily | Once per day at a chosen time |
| Weekly | Once per week on a chosen day and time |
| Custom cron | Any valid 5-field cron expression |
After selecting a preset, the wizard prompts for time details. For example, choosing "Daily" asks for the hour and minute; choosing "Weekly" asks for the day, hour, and minute.
Timezone Handling¶
Schedules default to UTC if no timezone is specified. To use a local timezone:
Daylight Saving Time
When using timezones with DST, schedule times shift with the clock. A 6 AM schedule runs at 6 AM local time year-round, which means the UTC time changes when clocks spring forward or fall back.
dbt Selectors (for type: dbt schedules)¶
When using type: dbt, specify a dbt command with selectors:
Common dbt selector patterns:
# Run a specific model and its upstream dependencies
dbt_command: "run --select +fct_orders"
# Run all models in a directory
dbt_command: "run --select marts.*"
# Run models tagged for hourly refresh
dbt_command: "run --select tag:hourly"
# Run tests after a specific model
dbt_command: "test --select fct_orders"
Configuration Reference¶
schedules.yml Format¶
Schedules are stored in .dango/schedules.yml. The wizard writes this file automatically, but you can also edit it directly.
# .dango/schedules.yml
schedules:
- name: daily_sync
type: sync # sync | sync_only | dbt
cron: "0 6 * * *" # 5-field cron expression
sources: # Sources to sync (ignored for type: dbt)
- stripe
- google_sheets
enabled: true # Toggle without removing
timezone: "America/New_York"
notify_on: # Per-schedule notification override
- failure
- stale
- name: hourly_marts
type: dbt
cron: "0 * * * *"
dbt_command: "run --select tag:hourly"
timeout_minutes: 30 # Override default 60-min timeout
# Notification config (see Webhook Notifications for details)
notifications:
webhooks:
- name: slack_alerts
url: "https://hooks.slack.com/services/T.../B.../xxx"
format: slack
on_failure: true
on_success: true
on_stale: true
on_governance: true
on_analysis: true
stale_threshold_hours: 24
Schedule Field Reference¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Unique identifier. Lowercase alphanumeric + underscores. |
type | string | no | sync | Schedule type: sync, sync_only, or dbt. |
cron | string | yes | — | 5-field cron expression (minute hour day month weekday). |
sources | list | no | [] | Source names to sync. Required for sync / sync_only. |
enabled | bool | no | true | Set to false to pause without deleting. |
timezone | string | no | UTC | IANA timezone (e.g., America/New_York). |
start_date | datetime | no | — | Don't run before this date. |
misfire_grace_time | int | no | — | Seconds after scheduled time to still run a missed job. null = always run. |
timeout_minutes | int | no | 60 | Maximum execution time before the job is killed. |
notify_on | list | no | [] | Per-schedule notification override: failure, success, stale. |
dbt_command | string | no | — | dbt command for type: dbt schedules (e.g., run --select marts.*). |
Cron Expressions¶
Cron expressions use 5 fields:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, Sun=0 or 7)
│ │ │ │ │
* * * * *
| Symbol | Meaning | Example |
|---|---|---|
* | Every value | * * * * * = every minute |
*/N | Every N | */15 * * * * = every 15 minutes |
N | Specific value | 0 6 * * * = at 6:00 |
N,M | Multiple values | 0 6,18 * * * = at 6:00 and 18:00 |
N-M | Range | 0 9-17 * * * = every hour 9 AM–5 PM |
Execution History¶
Viewing History¶
Navigate to the Schedules page to see execution history for each schedule, including start time, duration, status, and error details.
Status Values¶
| Status | Meaning |
|---|---|
running | Job is currently executing |
success | Job completed without errors |
failed | Job encountered an error (after all retries) |
timeout | Job exceeded its timeout_minutes limit |
cancelled | Job was stopped by the user or during server shutdown |
History records are retained for 30 days, after which they are automatically cleaned up. Running records older than 24 hours are automatically marked as failed (stale detection).
Resilience & Recovery¶
Scheduled jobs have built-in retry and timeout handling:
Retry Behavior¶
| Attempt | Delay Before | What Happens |
|---|---|---|
| 1 | — | First attempt runs immediately at the scheduled time |
| 2 | 30 seconds | First retry after transient failure |
| 3 | 2 minutes | Final retry |
| (failure) | — | All retries exhausted, marked as failed |
After all 3 attempts fail, the job is marked as failed and a sync_failed webhook notification is sent (if configured).
Timeout¶
Jobs are killed after 60 minutes by default (configurable per-schedule via timeout_minutes). Timed-out jobs are marked with timeout status and do not retry.
Missed Run Recovery¶
If Dango was offline when a schedule was supposed to fire (e.g., laptop was asleep, server was rebooting), the missed run executes immediately on startup. This is controlled by misfire_grace_time=None (unlimited) and coalesce=True — multiple missed runs collapse into a single execution.
APScheduler internals
Dango uses APScheduler 3.x with AsyncIOScheduler for job scheduling. Key configuration:
- Job store: SQLite database at
.dango/scheduler.db— jobs persist across restarts - Executor: ThreadPoolExecutor — sync jobs run in threads without blocking the event loop
coalesce=True: Multiple missed runs collapse into one executionmisfire_grace_time=None: Missed jobs always run (no expiration window)max_instances=1: Only one instance of a job can run at a time — prevents overlapping syncs
Schedules run while dango start is active. On macOS, closing the terminal does not stop Dango — the process continues in the background. If your laptop sleeps, schedules pause. Missed runs fire when the process wakes.
If you need to restart Dango after closing the terminal, just run dango start again — it will detect and reuse the existing process, or start a new one if needed.
Schedules run 24/7 via the systemd service. They survive server reboots automatically (systemd restart + APScheduler job persistence).
Verification¶
After creating a schedule, verify it's working:
# List all schedules with their status
dango schedule list
# Check a specific schedule's details and next run time
dango schedule status daily_sync
Expected output from dango schedule list:
Schedules
─────────
Name Type Cron Sources Enabled Next Run
daily_sync sync 0 6 * * * stripe, google_sheets yes 2026-05-16 06:00
hourly_marts dbt 0 * * * * yes 2026-05-15 15:00
Troubleshooting¶
Schedule not firing
- Verify
dango startis running (local) or the server is online (cloud) - Check
dango schedule list— is the scheduleenabled? - Verify the cron expression:
dango schedule status <name>shows the next run time
Overlapping syncs
- APScheduler's
max_instances=1prevents overlapping runs of the same schedule - If a sync is still running when the next one is due, the new run waits until the current one finishes (with
coalesce=True, backed-up runs collapse into one)
Timezone confusion
- Schedules default to UTC. Check
dango schedule status <name>to see the next run in your local time - Use a valid IANA timezone string (e.g.,
America/New_York, notEST)
Cron syntax errors
- Cron must be exactly 5 fields separated by spaces
- The wizard validates syntax before saving — if editing
schedules.ymlmanually, rundango config validateto check
Stale "running" status
- If a schedule shows
runningfor more than 24 hours, it was likely killed without cleanup (e.g.,kill -9) - Stale running records are automatically marked as
failedduring the next cleanup cycle - Restart
dango startto clear the state
Next Steps¶
- Webhook Notifications — get alerts on sync success, failure, or staleness
- Configuring Monitors — set up automated metric tracking after syncs
- Schema Drift — detect source schema changes between syncs
- CLI Schedule Commands — full CLI reference for schedule management