Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

šŸ’šŸ»ā€ā™€ļø Notion Boyfriend

If you're relatively new to programming, there's some helpful, more detailed explainer stuff at the end of the README, so if anything's confusing you may find answers there before you have to search around or talk to an LLM.

My boyfriend's back

One thing I enjoy in life is getting guys to do stuff for me. Hang this TV, build these shelves. It makes me feel like Galadriel when she bugs out in Fellowship.

And I thought, why am I here manually marking this task overdue? Why am I manually doing anything? Manual is for mans. So I'm building Notion Boyfriend to automate this kind of stuff for me. A handsome little set of Deno scripts in a nice jacket to whom I can say, "Baby can you email a list of my 3 highest priority tasks organized by [xyz] criteria" while I kick back and sip an ice cold Diet Coke.

The best part about Notion Boyfriend is you can make your own, then make him your own, the sky's the limit. Really there's no limit. Unlimited boyfriend, that's the power of Deno and Val Town.

Utilities

Each util lives in its own top-level file with a default export that wires up its trigger (cron, HTTP, or webhook). In Val Town, point the configured trigger type at the file.

label_overdue.ts

Walks every configured Tasks data source, finds pages whose Due date is before today (in America/Chicago) and whose Status is "not started" or "in progress" flavored, and flips them to the Overdue status. Set conditional formatting on the Overdue value in Notion to make rows pop red.

Trigger: cron (or HTTP for manual runs). Set around 2:30am America/Chicago — Val Town crons run UTC, so use 30 7 * * * during CDT or 30 8 * * * during CST.

Required env vars:

  • NOTION_TOKEN — internal integration token from developers.notion.com
  • NOTION_TASK_DATA_SOURCE_IDS — comma-separated bare data source IDs (no quotes)

See docs/label_overdue.md for status-variation extension points and internals.

cascade_access.ts

Notion guests get database-wide access by default — there's no built-in way to gate visibility down a relation chain. This util fakes row-level inheritance by cascading a Team people-property from Organization → Project → Task. Combined with Notion's data-source-level access rules that key off the Team property, each client guest only sees their own Org and everything underneath it.

Trigger: HTTP webhook from a Notion automation on the Organization data source. Organization is the source of truth; the cascade only flows downward. Manual additions to a Project's or Task's Team will be overwritten the next time its parent Org fires the webhook.

Flow on each webhook:

  1. Authenticate the request via the X-Notion-Webhook-Secret header (constant- time compare; rejection paths return a bare 404).
  2. Read the triggering Org page; walk its Projects relation to find affected projects, and each project's Tasks relation to find affected tasks.
  3. For each affected project, set Team to the union of its parent Orgs' teams. Skip writes when already correct.
  4. For each affected task, set Team to the union of its parent Projects' teams (post-update). Same skip/warn behavior.

Required env vars:

  • NOTION_TOKEN
  • NOTION_WEBHOOK_SECRET — shared secret sent by Notion as X-Notion-Webhook-Secret
  • NOTION_ORGANIZATION_DATASOURCE_ID
  • NOTION_PROJECT_DATASOURCE_ID
  • NOTION_TASK_DATASOURCE_ID

Required Notion setup:

  • Every Org, Project, and Task data source has a property named Team of type People.
  • Org has a relation property named Projects pointing at the Project DS.
  • Project has a relation property named Organization pointing at the Org DS (the bi-directional reflection of Projects).
  • Project has a relation property named Tasks pointing at the Task DS.
  • Task has a relation property named Project pointing at the Project DS (the bi-directional reflection of Tasks).
  • The integration is shared into all three data sources.
  • A Notion automation on the Org DS triggers on Team changes (and any other field whose change should propagate) and sends a webhook to this util's URL with X-Notion-Webhook-Secret set to NOTION_WEBHOOK_SECRET.

See docs/cascade_access.md for auth, dispatch, and cascade internals.

Project layout

notion-boyfriend/ ā”œā”€ā”€ label_overdue.ts # overdue-labeling util (cron + http) ā”œā”€ā”€ cascade_access.ts # team-cascade util (webhook) ā”œā”€ā”€ shared/ │ └── notion.ts # Notion client, env helpers, sleep, trigger wrapper ā”œā”€ā”€ docs/ # internal reference docs (see AGENTS.md index) ā”œā”€ā”€ deno.json ā”œā”€ā”€ README.md └── AGENTS.md

Configuration

All configuration is via Val Town environment variables — never bake IDs or tokens into the source. Utils fail fast at startup if any required env var is missing or empty.

For beginners

(coming soon)