Back to docs

Local scheduling

Mnueron ships two scheduled jobs: Granola sync (every ~10 min) and the nightly archive worker (once daily). Vercel Hobby caps you at one cron slot, so we recommend triggering both from a local scheduler instead. Same outcome, zero cost, no upgrade pressure.

The mental model

The deployed app exposes two cron-style endpoints that accept an x-cron-secret header. Anything that can send an HTTP GET with one header can be your scheduler — Windows Task Scheduler, macOS launchd, plain cron, GitHub Actions, even a Raspberry Pi on your desk.

  • GET /api/integrations/meet/granola/sync — pulls new Granola notes for every org.
  • GET /api/cron/archive — runs the archive worker for every org with a configured backend.

One-time setup

  1. Generate a strong CRON_SECRET. 32+ random characters. PowerShell:
    $bytes = New-Object byte[] 32
    [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
    [Convert]::ToBase64String($bytes)
  2. Set CRON_SECRET in Vercel. Project Settings → Environment Variables → add for Production. Redeploy so it takes effect.
  3. Set CRON_SECRET in your local .env. Same value. Used by the scripts in scripts/sync-granola.mjs and scripts/archive-run.mjs.
  4. Set SYNC_TARGET_URL in your local .env. Default is http://localhost:3111 (local dev). For production scheduling, set it to your deployed URL (e.g., https://www.mnueron.com).
# C:\Mnueron\ai-boilerplate-pro\.env
CRON_SECRET=<the-base64-value>
SYNC_TARGET_URL=https://www.mnueron.com

Manual run (anytime)

cd C:\Mnueron\ai-boilerplate-pro
npm run sync:granola
npm run archive:run

Each script prints what it did: how many notes ingested, how many duplicates, any errors. Useful for verifying everything works before automating.

One-off against localhost instead of production:

npm run sync:granola -- --url http://localhost:3111

Windows Task Scheduler — every 10 minutes

  1. Open Task SchedulerCreate Task… (not "basic task" — you need the repeat-every-N options).
  2. General tab: name "Mnueron Granola Sync", check "Run whether user is logged on or not".
  3. Triggers tab → New:
    • Begin: On a schedule
    • Settings: Daily, start time today 12:00 AM, recur every 1 day
    • Advanced: Repeat task every 10 minutes for a duration of 1 day
    • Stop task if runs longer than: 5 minutes
  4. Actions tab → New:
    • Action: Start a program
    • Program/script: npm
    • Arguments: run sync:granola
    • Start in: C:\Mnueron\ai-boilerplate-pro
  5. Conditions tab: optionally uncheck "Start only on AC power" so it runs on battery too.
  6. OK → enter your password when prompted. First run validates everything.

Archive job — once a day at 3am

Same flow as Granola sync, but a different schedule:

  • Task name: Mnueron Archive
  • Trigger: Daily at 3:00 AM. No repeat needed.
  • Action: npm with arguments run archive:run
  • Start in: C:\Mnueron\ai-boilerplate-pro

mac / linux cron

If you'd rather use real cron:

# Granola every 10 min
*/10 * * * *  cd /path/to/ai-boilerplate-pro && /usr/local/bin/npm run sync:granola >> /var/log/mnueron-granola.log 2>&1

# Archive nightly at 3am
0 3 * * *      cd /path/to/ai-boilerplate-pro && /usr/local/bin/npm run archive:run >> /var/log/mnueron-archive.log 2>&1

Use the full path to npm (find with which npm) because cron's PATH is minimal.

Security note

The CRON_SECRET is the only thing protecting the cron endpoints from unauthenticated traffic. Treat it like a password: generate a long random value, never commit it, rotate if you suspect leakage. If you ever see suspicious POST volume on these endpoints in Vercel logs, rotate immediately.