Schedules
What runs on a timer, when, and what you see when it works or when it fails.
On this page
DispatchSEO's automation runs across three different schedulers, not one - and the split isn't arbitrary. Vercel's Hobby plan (what the hosted dispatchseo.com and most Vercel self-hosts run on) caps scheduled jobs at once a day and two jobs total, so anything that needs to run more often than that lives in GitHub Actions instead - either this repo's own workflows (for the backend's own data crons) or the 11 workflows installed into your connected site's repo (for the content pipeline). A self-hosted Docker install has neither constraint, but it has a different one: a home machine or a fresh VPS usually has no public URL for GitHub Actions to call back into, so it runs its own cron container plus an in-stack builder instead, both of which only ever make outbound connections.
Backend crons
These are the jobs that keep your numbers current - rank tracking, Search Console stats, and the post-deploy smoke test. They loop over every project on the instance; nothing here is specific to one site.
Runs
Vercel: daily at 04:00 UTC (vercel.json). Docker: the cron container, same schedule.
Needs
Nothing to be turned on - it loops every project and skips per-project gates on its own.
If it fails
Red banner on Home, alert email if configured, shown in get_cron_health as daily-ranks.
daily-ranks checks where you rank for every tracked keyword, using whichever source your project is set to: DataForSEO mode posts cheap standard-queue tasks here and the actual rank rows land later via serp-collect below; SerpApi mode checks live, but only on Mondays, to stay inside the free 250-search monthly quota; GSC mode reads average position from Search Console, for free, on every run. The same pass also snapshots yesterday's Search Console clicks and impressions, and refreshes your domain rating once it's aged past a week.
Runs
Cloud: GitHub Actions every 3 hours at :07. Docker: the cron container, same cadence.
Needs
Search Console connected - skips as an informational no-op otherwise.
If it fails
Same rails as daily-ranks, job name hourly-gsc.
hourly-gsc re-snapshots the last few days of Search Console data ( today's numbers are provisional and firm up as the day goes on), checks up to 10 newly-published pages to see if Google has indexed them yet, and piggybacks two maintenance sweeps: recovering any build that got stuck mid-way, and firing fresh research for a project whose guide queue has run completely dry.
Runs
Cloud: GitHub Actions hourly at :37. Docker: the cron container, same cadence.
Needs
DataForSEO connected with tasks actually pending - most projects on GSC or SerpApi have nothing to collect and don't appear in the report at all.
If it fails
Same rails, job name serp-collect.
serp-collect is daily-ranks' companion: it fetches the DataForSEO
standard-queue SERP tasks daily-ranks posted and writes the actual
rank_checks rows, usually 30-60 minutes later. Without this job,
DataForSEO-mode rank tracking would queue tasks that simply never land.
Runs
After every push to main (cloud) - the workflow polls until the pushed commit is actually live, then runs the check.
Needs
Nothing - runs automatically on every deploy.
If it fails
Same rails, job name deploy-check.
deploy-check is the post-deploy smoke test: it confirms the core tables are reachable, projects resolve correctly, GSC credentials still parse, and (in cloud mode) that billing and the GitHub App are configured. It also doubles as the generic outcome-reporting door for everything else on this page - every workflow below, on both schedulers, reports its own success or failure back through this same endpoint under its own job name, which is how a broken workflow in your site's repo shows up on your dashboard instead of dying quietly in the GitHub Actions tab.
weekly-opportunities is retired - it's not on any schedule anymore (the
Monday research workflow described below replaced it), but the route still
answers if you call it by hand. It only ever covered the instance's default
project and has no toggle, so nothing else on your dashboard depends on it.
Workflows in your site's repo
When you connect a site, DispatchSEO installs 11 GitHub Actions workflows
into that repo's .github/workflows/ folder. These are what actually do
the work of the pipeline - researching keywords, writing guides, building
tools, validating them, and merging the result - while the backend crons
above only track your numbers. Because they run on GitHub's own runners,
they need a public URL to call back into your dashboard; that's exactly
the constraint the self-hosted Docker builder (below) exists to route
around when you don't have one.
Runs
Up to three times a day (05:13, 12:13, 19:13 UTC) - later attempts are a no-op once one has already built today.
Needs
An approved guide or tool suggestion waiting, and no SEO PR already open.
If it fails
Reports job=seo-daily to your dashboard - banner, email, and get_cron_health.
SEO daily builder (seo-daily.yml) builds the oldest approved
suggestion into a PR: get_instructions → template → live SERP gate →
humanizer voice pass → PR. Ships at most one guide a day, and the three
same-day attempts exist so a Claude usage-limit hit is a quiet deferral
instead of a lost day.
Runs
Event-driven - the moment any check reports, plus an hourly backstop sweep.
Needs
Hands-off publishing turned on, an open PR labeled seo, every check green, and (for guides) every changed file under your repo's own .dispatchseo/publish-paths.
If it fails
Reports job=seo-auto-merge.
SEO auto-merge (seo-auto-merge.yml) is the hands-off publish gate. A
tool PR additionally needs the tool-validated label from SEO tool
validation below before this workflow will touch it - structural changes
outside the publish paths are never auto-merged, regardless of this
setting.
Runs
Mondays, up to three attempts (06:23, 13:23, 20:23 UTC) so a dropped run self-heals the same day instead of losing the week.
Needs
Nothing you have to enable - this is what fills the queue in the first place.
If it fails
Reports job=seo-weekly-research.
SEO weekly research (seo-weekly-research.yml) reads your product as
it exists that week, derives keyword ideas from it, validates them through
DataForSEO, tracks the winners, and queues suggestions - guides
pre-approved, tool ideas pending your review. It also fires on demand the
moment your guide queue runs completely dry, so a missed Monday run never
leaves the daily builder with nothing to publish.
Runs
Wednesdays, up to three attempts (06:27, 13:27, 20:27 UTC).
Needs
Nothing you have to enable.
If it fails
Reports job=seo-geo-scan.
SEO geo scan (seo-geo-scan.yml) measures AI visibility: it builds the
questions your customers would ask an AI assistant, answers each one with
real web search on your own Claude subscription, and records which answers
cite your site. No PR - results land directly on the dashboard's
AI-visibility section.
Runs
Instantly when you approve a tool suggestion, plus a Wednesday weekly sweep (05:42, 12:42, 19:42 UTC) as a backstop.
Needs
An approved tool suggestion waiting.
If it fails
Reports job=seo-tools.
SEO tool builder (seo-tools.yml) builds the oldest approved tool idea
into a PR - template, live SERP gate, theme discovery, an execution plan
held against the pipeline's value bar (a reskinned template or a
canned-output widget fails this check), then the build itself, labeled
seo and seo-tool.
Runs
On every tool PR (opened, updated, reopened, or labeled).
Needs
A PR labeled seo-tool - guide PRs never trigger this.
If it fails
Labels the PR tool-validation-failed with the concrete failures as a comment - no dashboard alert, since a failing tool PR is a normal, actionable outcome, not a broken schedule. Only the merge job reports to the dashboard (job=seo-tool-validate); the validation job itself runs LLM-authored PR code and deliberately holds no secrets.
SEO tool validation (seo-tool-validate.yml) is the functional
reviewer for tool PRs: it builds the branch for production, exercises the
tool in a real browser, and only a pass earns the tool-validated label
that auto-merge requires.
Runs
Daily at 04:18 UTC, ahead of the 05:13 builder.
Needs
Nothing you have to enable.
If it fails
Reports two job names: seo-token-check (a dead Claude token) and seo-pipeline-version (an outdated pipeline pack - shown as an update notice, not a failure).
SEO health check (seo-token-check.yml) proves your Claude token still
works with one minimal model call, before the day's build attempts depend
on it, and checks whether your installed pipeline is a version behind.
Because it runs every day without fail, its own silence over time is a
useful signal too - see the pipeline heartbeat note below.
Runs
Once, dispatched by the backend right after it installs the pipeline (cloud zero-touch onboarding), or by hand.
Needs
Nothing - this workflow is the setup step.
If it fails
Reports job=seo-setup.
SEO setup (seo-setup.yml) is the one-time personalization run: it
reads your repo, finds or scaffolds the content home, writes
.dispatchseo/conventions.md, fills in the site profile, then confirms
itself back to the backend. Safe to re-run any time - it reconciles rather
than duplicating.
Runs
Once, dispatched by setup.sh at the end of an install.
Needs
Nothing.
If it fails
Fails visibly in the GitHub Actions tab during install - this is a one-time proof, not an ongoing schedule, so it isn't something you'll see fail months later.
SEO install canary (seo-canary.yml) opens a real, throwaway PR from
inside a workflow - the exact permission GitHub's default settings block on
brand-new repos - then closes it and deletes the branch. Nothing is ever
merged. If any repo permission is wrong, this is where the install fails,
in front of you, instead of three days later at 05:00.
Runs
Manual only - fires when you click "Scan now" on the Trends page.
Needs
Nothing - always available, no schedule on purpose.
If it fails
Reports job=seo-trend-scan.
SEO trend scan (seo-trend-scan.yml) is stage one of the trend radar:
it reads your niche for subjects being talked about right now - launches,
Reddit and Hacker News buzz, Google Trends - and puts them on the radar
with evidence. It proposes no guide ideas and spends no SERP budget; that's
stage two, below.
Runs
Fires when you click "Get takes" on a trend-radar subject.
Needs
A subject on the radar for you to pick.
If it fails
Reports job=seo-trend-expand.
SEO trend expand (seo-trend-expand.yml) is stage two: it turns the
one subject you picked into 3-5 validated guide angles, queued as pending
suggestions - this is where SERP validation spends money, which is exactly
why it only ever runs on a subject you chose.
Self-hosted Docker
A Docker install runs neither the Vercel cron nor the GitHub Actions
workflows above by default - it has its own cron container and its own
builder container, both defined in docker-compose.yml, that only ever
make outbound connections. That's what makes a fully-automatic install
possible on a machine with no public URL at all.
Runs
BusyBox crond, hitting the same three endpoints as the cloud schedule above: daily-ranks at 04:00 UTC, hourly-gsc every 3 hours at :07, serp-collect hourly at :37.
Needs
Nothing - always on.
If it fails
Same banner/email/get_cron_health rails as every other job on this page. docker compose logs cron shows every hit as it fires.
The cron container (docker/cron/crontab) deliberately does not
schedule weekly-opportunities, even though it's part of the same file:
that route is retired, hard-scoped to a single project, duplicates
research the builder below already runs on its own cadence, and spends
DataForSEO balance with no toggle to turn it off. It stays callable by
hand; it just no longer runs behind your back.
Runs
Polls /api/builder/jobs every BUILDER_POLL_SECONDS (default 600s / 10 minutes) and executes whatever comes back.
Needs
A Claude Code OAuth token (dashboard "Turn on automatic builds" step, or CLAUDE_CODE_OAUTH_TOKEN in .env) - without one it idles and says exactly what it's waiting for.
If it fails
Each job reports under builder-<job>--<slug>, same rails as everything above.
The builder container is the in-stack replacement for the four schedule-driven workflows above (research, build-guide, build-tool, geo-scan) - the scheduling logic lives entirely on the backend, so cadence fixes ship as ordinary backend deploys, never as "rebuild your builder image." It runs each job with headless Claude Code inside a clone of your site's repo: research and geo-scan roughly every 6.5 days, build-guide and build-tool roughly daily (20-hour windows, so a run that fired a bit early yesterday is still due today). If the builder claims a job and then dies before reporting back - a crash, a bad token - the claim expires after 3 hours and the job becomes due again, rather than sitting falsely "done" for the rest of its whole cadence window.
It also runs its own merge sweep for auto-merge projects: it checks
every open, green seo-labeled PR against your repo's own
.dispatchseo/publish-paths, and merges the ones that qualify -
functionally the same gate GitHub's seo-auto-merge.yml enforces on cloud,
just running from inside the stack instead.
What happens when a job fails
Every job on this page - backend cron, workflow, or builder job - reports
its outcome through the same function, reportCronRun(). A run always
gets logged to the cron_runs table; a failed run additionally triggers,
in order:
- A red banner on the dashboard Home page.
- An email through Resend, if you've set up failure alerts - debounced so a job that stays broken doesn't spam you: 24 hours for the three backend data crons, 6 hours by default for everything reported from GitHub Actions or the builder, and every single time for deploy-check, since each of its failures is a distinct human push. A one-off vendor blip (a transient DataForSEO or SerpApi error) gets one free pass too - the email only goes out if the same job also failed on its previous run.
- Visibility in the
get_cron_healthMCP tool, so your agent can see exactly what's broken and, once you've actually fixed and verified it, clear the alert itself withmark_cron_fixed.
See Troubleshooting for the fixes to the specific failures you're most likely to hit.
Setup-incomplete vs. a real failure
A cron or workflow that hits a prerequisite you haven't finished setting up yet - no Search Console connected, no DataForSEO credentials, a project still mid-wizard - returns an informational "skipped: setup incomplete" result and stops there. That is never treated as a failure: no red banner, no email, no HTTP 500. Being half set up is a completely normal, expected state while you're onboarding - the dashboard's Home page has its own "Initial setup" cards for exactly this, and a cron that panicked every time a project wasn't fully configured yet would make every fresh install look broken from minute one.
Loudness is reserved for regressions instead: a capability that has demonstrably worked before - data rows already exist, credentials were validated when you saved them - and then fails is treated as broken and alerts every time, because that's the difference between "you haven't gotten here yet" and "something that used to work just stopped."
Running on a machine that sleeps
If you're self-hosting on a laptop rather than an always-on VPS, a missed schedule is a delay, not a failure. Docker's staleness checks measure how long the app container has actually been running, not wall-clock time - so a machine that was asleep for ten hours wakes up owing ten hours of schedules, not flagged as broken for having missed them. Content builds catch up on their own once the machine wakes (the builder checks what's due within its next poll), and only a job that misses its window while the stack is actually up counts as a real failure. You'll never get an alert for a job that was simply powered off.
DispatchSEO