Goal: run something on a schedule with no server to maintain.
Wiring: Cloud Scheduler job -> Pub/Sub topic -> push subscription -> Cloud Run service. Or Scheduler -> Pub/Sub -> pull subscriber, if you prefer.
Create the job:
```
gcloud scheduler jobs create pubsub [JOB] --schedule "0 2 * * *" --topic [TOPIC] --message-body "[PAYLOAD]" --time-zone "America/Los_Angeles"
```
Traps:
- Time zone: cron runs in the job's time zone, default UTC. "2am" in UTC is not 2am local. Set --time-zone explicitly.
- Scheduler needs pubsub.topics.publish on the topic. The Scheduler SA is per-project; grant it once.
- The message body is opaque to Scheduler; put routing info in attributes or the body JSON your handler parses.
- Exactly-once is not a thing here; the handler must be idempotent (dedupe on message_id or a run id in the payload).
- Overlapping runs: if a run takes longer than the interval, two run concurrently. Design for it or set the schedule to avoid overlap.
- Retries: configure retry config on the Scheduler job AND a dead-letter policy on the subscription. A failing handler otherwise retries on two layers and confuses everyone.
Cheaper alternative for simple HTTP: Scheduler HTTP target directly at the Cloud Run URL with OIDC auth, skipping Pub/Sub. Use Pub/Sub when you want fan-out, retries, or decoupling.
Verify: trigger the job manually once (`gcloud scheduler jobs run`), confirm exactly one correct execution, then let the schedule fire and watch.