Your Python cron jobs are failing silently. Here's how to fix it.
If you're running a Python app on Vercel, Railway, Render, or Fly.io, you've run into
this: there's no persistent process, so you can't use cron, APScheduler,
or Celery workers the traditional way. Most developers end up using the platform's
built-in scheduled tasks — which work fine until they silently stop running and nobody
notices for three days.
I built Tickstem to solve this, and just shipped a Python SDK.
The silent failure problem
Cron jobs fail in two ways.
Loud failures — the job runs, your endpoint returns 500, you get an alert. Painful, but you know about it immediately.
Silent failures — the job never runs at all. Maybe a deployment broke something, maybe a config changed, maybe the scheduler just quietly dropped it. The endpoint is healthy, no errors anywhere, but the job just stopped. You find out when a user asks why their weekly report didn't arrive.
The second type is what kills you. Uptime monitoring doesn't catch it because your server is up. Error tracking doesn't catch it because there's no error. You need a different approach.
Install
pip install tickstem
Requires Python 3.11+. One package, one API key, four tools — cron scheduling, uptime monitoring, heartbeat monitoring, and email verification.
Scheduling cron jobs
Instead of running a scheduler inside your app, Tickstem calls your HTTP endpoint on a schedule from outside. This works on any serverless platform without touching your app's runtime.
from tickstem import CronClient, CronRegisterParams
client = CronClient(os.environ["TICKSTEM_API_KEY"])
job = client.register(CronRegisterParams(
name="send-weekly-report",
schedule="0 9 * * 1", # every Monday at 9am UTC
endpoint="https://yourapp.com/jobs/weekly-report",
))
print(job.id, job.next_run_at)
Your endpoint just needs to return 2xx. No SDK required on the receiving side — Tickstem makes a plain HTTP request. Every execution is logged with the response body, status code, and duration, and you get an email on the first failure.
The dead man's switch — solving silent failures
This is the part that actually solves the silent failure problem. Instead of Tickstem calling your endpoint, your job calls Tickstem at the end of every successful run. If the pings stop arriving within the expected window, Tickstem sends an alert.
from tickstem import HeartbeatClient, HeartbeatCreateParams
client = HeartbeatClient(os.environ["TICKSTEM_API_KEY"])
# Create once — save the token somewhere permanent
hb = client.create(HeartbeatCreateParams(
name="weekly-report",
interval_secs=604800, # expect a ping every 7 days
grace_secs=3600, # 1 hour buffer before alerting
))
# At the end of your job handler:
try:
client.ping(hb.token) # token is the credential — no API key needed
except Exception as e:
logging.warning(f"heartbeat ping failed: {e}") # non-fatal
The ping endpoint only requires the token, not your API key. This means you can call it from any script or service without storing sensitive credentials there. If the ping fails (network blip), log it and move on — never let a monitoring call block your job from completing.
Heartbeat monitoring catches what uptime monitoring misses: jobs that silently stop running rather than servers that go down. You need both.
Uptime monitoring with response assertions
Tickstem polls your HTTP endpoints and alerts you when they go down or recover. Response assertions let you define what "healthy" actually means — not just that the server responded, but that it responded correctly.
from tickstem import UptimeClient, UptimeCreateParams, Assertion
client = UptimeClient(os.environ["TICKSTEM_API_KEY"])
monitor = client.create(UptimeCreateParams(
name="Production API",
url="https://api.yourapp.com/health",
interval_secs=60,
assertions=[
Assertion(source="status_code", comparison="eq", target="200"),
Assertion(source="response_time", comparison="lt", target="2000"),
Assertion(source="body", comparison="contains", target='"status":"ok"'),
],
))
checks = client.checks(monitor.id, limit=50)
for check in checks:
print(check.status, check.duration_ms, "ms")
SSL certificate expiry is tracked automatically — you get an alert 30 days before a certificate expires, so you're never caught by a surprise outage.
Email verification
The fourth tool in the bundle — validate email addresses before storing them.
Checks syntax, MX records, 200+ known disposable email services, and role-based
prefixes like admin@ or noreply@. No SMTP probing.
from tickstem import VerifyClient
client = VerifyClient(os.environ["TICKSTEM_API_KEY"])
result = client.verify("[email protected]")
if not result.valid:
raise ValueError(f"Email rejected: {result.reason}")
if result.disposable:
raise ValueError("Disposable email addresses are not allowed.")
One API key for everything
All four tools share one API key and one plan. Free tier includes 1,000 cron executions, 5 uptime monitors, 5 heartbeats, and 500 email verifications per month. No credit card required.
pip install tickstem
- GitHub: github.com/tickstem/python
- Docs: tickstem.dev/docs
- Free tier: app.tickstem.dev