Getting Cron to Actually Trigger on Time
So the idea was simple: use Cron to schedule recurring content reminders in Notion so I wouldn’t forget to publish blog posts every other Tuesday. What I wanted was to have a nice clean task pop into my Notion dashboard like “Draft automation post” on a repeating schedule. What happened was… it just never ran. Or it ran exactly once and then gave up forever ¯\_(ツ)_/¯.
The first hiccup I hit was understanding how Cron supports repeating intervals. Cron doesn’t use human language like “every week on Tuesday.” It uses these five-slot syntax strings like `0 9 * * 2` (this one is every Tuesday at 9am). I messed up the day-of-week field at least three times before I realized “oh, right, 0 is Sunday, 2 is Tuesday, stop guessing.”
You can use crontab guru (yes that’s the actual name) to test them, which helps—but even then, Cron wouldn’t fire when I thought it should. Turns out my local timezone was different than Cron’s server timezone. I was expecting 9am tasks, but they were happening at 4am or simply silently failing who knows where.
Eventually I went into the Cron tool (I used cronjob dot org for testing) and added a print statement (`curl` call to a dummy URL I controlled) just to see if *anything* was triggering at all. Spoiler: it wasn’t. Once I fixed the syntax AND set the timezone explicitly in the tool settings, then it started pinging.
So heads up: don’t trust the default timezone. Set it manually and confirm it logs activity BEFORE moving on.
Getting Notion API Auth to Stop Failing
Okay so Cron is now pinging on schedule, but now I needed it to actually hit the Notion API to create or update a content task. This part got messy fast. First of all, Notion’s authentication expects a bearer token tied to a Notion integration. So you have to start by following Notion’s process to create an integration, and it gives you a long secret key string like `secret_xyzabcblahblah`.
What I messed up here (twice) was forgetting to share the page or database with the integration. In Notion, creating the integration is just step one. You then have to go into the database you want to modify, click the three dots top-right, pick “Add Connections,” and explicitly give your integration access to that page or database. Otherwise, API calls just return 403 errors and you’ll want to throw your laptop.
I used Postman (downloaded it again for like the twelfth time) to test the API call. You want to set the Authorization type to Bearer Token and paste your long secret token in. Then make a POST request to the `pages` endpoint if you’re creating new items.
Just for sanity, here’s a quick checklist before you even touch Cron:
– [ ] Integration created on Notion
– [ ] Secret stored/copied somewhere
– [ ] Integration added to Notion database (via Share > Add connections)
– [ ] You’ve confirmed the database ID (hint: open it in browser and copy the messy UUID-looking string from the URL)
– [ ] A valid cURL or Postman request successfully creates a new item
Don’t skip that last one. It’s super tempting to plug everything into Cron and *then* debug, but Notion will reject badly formed payloads.
Structuring JSON to Match Notion Expectations
The ridiculously picky part is the actual JSON body. Notion doesn’t use simple key-value pairs. Every “property” has a nested object structure depending on the data type. For text titles, it expects something like this:
“`json
“properties”: {
“Name”: {
“title”: [
{
“text”: {
“content”: “Write blog post draft”
}
}
]
}
}
“`
Yeah. That’s just to set a text title. Dates, selects, checkboxes—they all have different shapes. What threw me off is that if a single character is wrong or missing (like using `name` instead of `Name`, or forgetting the `content` layer), Notion doesn’t gracefully fail. It gives you a vague error like “Invalid property value” or just returns a status code without comment.
I had to copy-paste working examples from their docs and then modify *one field at a time* until it was shaped right. What finally helped was creating a test item manually in Notion, then using Notion’s developer tools to inspect what the API representation looked like. That at least let me mimic a working structure.
Another gotcha: the property names in the JSON must match EXACTLY with the headings in the Notion database. Even a lowercase mismatch or an extra space means nothing shows up. So when I tested, I renamed the Notion column from “Headline” to “Name” because `Name` is what the API expects for the title.
How to Send a Web Request from Cron
Once you have a working cURL or webhook setup for Notion, you can paste that into Cron (depending which tool you’re using). Some online Cron tools let you define a URL to request on a schedule, which is great. Others require running a shell script, which adds another layer.
My quick-and-dirty method was to host a script on Replit that sends the Notion API request, and then point Cron to hit that Replit endpoint every Tuesday. It’s janky, yes, but it isolated the Notion logic from Cron’s behavior. You can use a simple Express.js snippet like:
“`js
app.post(“/schedule”, async (req, res) => {
await fetch(“https://api.notion.com/v1/pages”, {
method: “POST”,
headers: {…},
body: JSON.stringify({ your_note_data_here })
});
res.sendStatus(200);
});
“`
Then from Cron you just hit that `/schedule` endpoint every week. That’s currently what I’m still using because directly pasting JSON bodies into Cron tools (like those that only accept raw GET requests) was super unreadable and brittle.
How It All Fails Quietly When Something Breaks
Once it was all connected, things ran fine…for about two weeks. Then one Tuesday I opened Notion expecting to see a new “Write draft” task in my Content Planning database…and nothing. I checked Cron logs—it had fired. I checked the Replit logs—the webhook endpoint was hit. I checked the actual payload and saw: oh no, the integration token had expired.
Apparently when you re-generate a token or accidentally create a new secret, Notion doesn’t automatically revoke the old one, but it might stop recognizing it anyway. I had copy-pasted a new token into my test environment but forgot to update it in the Cron scheduler.
I almost missed publishing that week because in my head “the automation is taking care of it.” Nope. The automation broke in silence. No error messages. Nothing in Notion. No emails. Just…nothing happened :).
After that I added two more things:
– I used Cron’s optional email notifications for success and error responses, with separate subjects
– I added a Zap to trigger a Slack alert if the Notion entry *doesn’t* show up by 10am on Tuesdays
Basically I had to automate the monitoring of the automation. Yeah.
Fixing Limitations with Notion Templates
At one point I tried to use Notion’s internal templates to prefill recurring tasks—like having a pre-set checklist for each blog post. But good luck triggering those via API.
If you’re thinking “oh, I’ll just duplicate a template for new posts,” you can’t. The API doesn’t support duplicating templates. You have to rebuild the same structure via API by hardcoding it in. That means if your template uses subtasks, predefined selects, date formulas—you’ll need to manually recreate the fields in your payload.
So my workaround was to store a JSON blob of the full desired task object (with all properties filled in) and POST that each week. It’s fragile though. If I change the structure in Notion later, my automated version won’t match.
In short, Notion templates are for human clicks. API interactions can’t touch them yet. Maybe someday. Maybe.
Debug Order That Actually Helped Me
If you end up setting this up yourself, I HIGHLY suggest this debugging order so you don’t waste three hours wondering why your webhook was never called:
1. Start with Cron only – just print/log to confirm the trigger works
2. Strip out Notion completely – make Cron hit a dummy endpoint first
3. Build and POST to Notion from a manual script or Postman
4. Confirm the Notion page shows up with correct fields
5. ONLY THEN connect them together
If anything goes wrong later, you’ll know which stage failed. Otherwise you get these ghost failures with no logs, no UI, and your content never shows up.
(honestly thinking of just going back to a repeating Google Calendar event that yells at me. But we’re here now, so…)
Why Monitoring Your Own Recurring Stuff Matters
This sounds obvious, but I’m gonna say it anyway: when you automate recurring stuff, you forget it exists. And if it stops working, you usually won’t notice for days. That makes breakage especially sneaky.
One week I didn’t get a new task in Notion and just assumed “oh I probably wrote it already.” I hadn’t. It was never generated. When I went back into the logs, the Notion API had failed validation because I had added a new required property called “Category” and never updated my JSON payload.
So I now keep a recurring quarterly reminder called “Check automations for silent failure.” It just reminds me to review my Cron job results and audit Notion entries manually. It’s annoying. But the alternative is ghost bugs forever.
If you build something like this, definitely add a basic notification system somehow. Slack, email, even a blinking LED light tied to a Raspberry Pi. Anything that reminds you “hey, the robot flinched.”
¯\_(ツ)_/¯