Standalone worker that keeps a Notion intake/reporting database, GitHub Issues, and GitHub Projects in tune, with GitHub as the source of truth.
The current flow is:
Notion Form response -> GitHub Issue -> GitHub Project item -> Notion mirror fields
The production GitHub Project for this worker is:
- Project:
Marketing Projects - URL: https://github.com/users/leebaroneau/projects/4
- Project v2 node ID:
PVT_kwHOAfzT1c4BX_7o
GitHub owns operational state:
- issue state
- assignee
- labels
- milestone
- project status
- project priority
- roadmap / track
- request type
- target date
- owner
- source
Notion can initiate only narrow GitHub-shaped commands:
- create the initial issue from the form response
- post a requester follow-up as a GitHub issue comment
- request a configured priority label such as
priority: high
The worker overwrites Notion mirror fields from GitHub on each pass. Do not use Notion-only status fields for engineering workflow state. If a GitHub Project value changes, the next poll writes that value back to Notion.
Create a Notion Form backed by a database with these properties. The exact names matter.
| Property | Purpose |
|---|---|
Title |
GitHub issue title |
Request Type |
Maps to type: <value> label |
Description |
GitHub issue ## Request section |
Business Context |
GitHub issue ## Business Context section |
Acceptance Criteria |
GitHub issue ## Acceptance Criteria section |
Requester Name |
GitHub issue requester metadata |
Requester Email |
GitHub issue requester metadata when NOTION_GITHUB_SYNC_INCLUDE_REQUESTER_EMAIL=1 |
Company |
GitHub issue requester metadata |
Business Priority |
Must exactly match one allowed GitHub priority label |
Requester Follow-up |
Text to post as a GitHub issue comment |
Send Follow-up |
Checkbox command that posts Requester Follow-up once |
Add these mirror and sync fields for the worker to write back:
GitHub Issue URL
GitHub Issue Number
GitHub Issue Node ID
GitHub State
GitHub State Reason
GitHub Labels
GitHub Assignee
GitHub Project Status
GitHub Project Priority
GitHub Project Roadmap
GitHub Project Request Type
GitHub Project Target Date
GitHub Project Owner
GitHub Project Source
GitHub Project Item ID
GitHub Milestone
GitHub Closed At
Last GitHub Sync
Last GitHub Project Sync
Sync Status
Sync Error
Last Sync Direction
Last Sync Actor
Last Notion Hash
Last GitHub Hash
Last Comment Hash
Last Processed At
Use Notion property types that match the field intent: number for issue number, URL for issue URL, checkbox for Send Follow-up, date for sync timestamps and target dates, multi-select for labels if you want labels split into options, and rich text for the rest.
Create the labels referenced by your form and env vars before enabling sync:
intake
from:notion
priority: low
priority: medium
priority: high
type: feature-request
type: bug-report
type: ops
type: content
type: support
type: other
needs:verification
GitHub Projects should be the canonical shape for the workflow. At minimum, configure these Project fields:
| Field | Recommended type | Purpose |
|---|---|---|
Status |
Single select | Workflow state: Inbox, Ready, In Progress, Blocked, Done, Wont Do |
Priority |
Single select | Low, Medium, High |
Roadmap |
Single select | Now, Next, Later |
Request Type |
Single select | Feature Request, Bug Report, Ops, Content, Support, Other |
Target Date |
Date | Planned delivery or review date |
Assignees |
Built-in people field | GitHub owner for the work; mirrored into Notion's GitHub Project Owner |
Source |
Single select | Notion, GitHub, Internal, Customer, Sales, Support |
The worker adds linked issues to the Project using GITHUB_PROJECT_ID. Newly-created Notion intake issues are initialized as:
Status = Inbox
Source = Notion
Request Type = <Notion Request Type>
Priority = <Notion Business Priority, converted from priority: high -> High>
Triage then happens in GitHub Projects. Notion reports what GitHub says.
- Intake lands in
Inboxfrom Notion or a GitHub issue template. - Triage sets
Request Type,Priority,Roadmap,Target Date, andAssignees. - Accepted work moves to
Ready. - Active work moves to
In Progress; blocked work moves toBlocked. - Work only moves to
Doneafter the issue's acceptance criteria have been verified. - Work that will not be done moves to
Wont Dowith a closing comment.
Repo guardrails:
- GitHub issue forms are enabled for feature, bug, ops, content, and support requests.
- CI runs
npm testand a Docker build on pushes tomainand on pull requests. - Pull requests include a verification checklist and require the Project status to reflect the real state.
One running worker targets one Notion database and one GitHub owner/repo.
NOTION_GITHUB_SYNC_ENABLED=1
NOTION_TOKEN=<notion integration token>
NOTION_INTAKE_DATABASE_ID=363333cc46cc8019b6d9cfed97ccd479
GITHUB_TOKEN=<fine-grained token with issues:write>
GITHUB_OWNER=leebaroneau
GITHUB_REPO=notion-github-sync
GITHUB_PROJECT_ID=PVT_kwHOAfzT1c4BX_7o
GITHUB_PROJECT_FIELD_STATUS=Status
GITHUB_PROJECT_FIELD_PRIORITY=Priority
GITHUB_PROJECT_FIELD_ROADMAP=Roadmap
GITHUB_PROJECT_FIELD_REQUEST_TYPE=Request Type
GITHUB_PROJECT_FIELD_TARGET_DATE=Target Date
GITHUB_PROJECT_FIELD_OWNER=Assignees
GITHUB_PROJECT_FIELD_SOURCE=Source
GITHUB_PROJECT_DEFAULT_STATUS=Inbox
GITHUB_PROJECT_DEFAULT_SOURCE=Notion
NOTION_GITHUB_SYNC_LABELS=intake,from:notion
NOTION_GITHUB_SYNC_ALLOWED_PRIORITY_LABELS=priority: low,priority: medium,priority: high
NOTION_GITHUB_SYNC_INCLUDE_REQUESTER_EMAIL=1GITHUB_PROJECT_ID is the Project v2 node ID, not the project number. You can find it with GitHub's GraphQL API or gh api graphql. The token needs issue write access for the repository and GitHub Projects access. For a classic token, use repo, read:org, and project; for a read-only Projects mirror, read:project is enough for queries, but this worker adds items to the Project, so it needs project mutation access.
To make it specific to a GitHub organization, set GITHUB_OWNER to the org, set GITHUB_REPO to that org's canonical intake repo, and use org-specific labels or a dedicated Notion database. For multiple orgs or separate canonical boards, run separate workers with separate env vars.
Create a Notion internal integration, copy its integration token into NOTION_TOKEN, and share the intake database with that integration. V1 does not require Notion webhooks because the worker polls and reconciles drift from GitHub back into Notion.
The Notion database needs two groups of fields:
- Intake command fields: fields that non-GitHub users fill in, such as title, request type, description, business context, acceptance criteria, requester, and follow-up.
- Mirror fields: fields this worker overwrites from GitHub Issues and GitHub Projects. Treat these as read-only in Notion.
If Notion property names differ from the names listed above, rename the Notion properties for V1. Configurable property-name mapping can be added later if needed, but exact names keep the first production version simpler and less drift-prone.
Coolify can deploy this directly from the Git repository. Use either:
- Dockerfile build pack: recommended for predictable worker runtime. Coolify builds the included
Dockerfilefrom this repo. - Nixpacks: possible because this is a plain Node app with
npm start, but less explicit than the Dockerfile path.
This worker does not need a public domain. Configure the env vars in Coolify and deploy it as a background service.
cp .env.example .env
npm test
npm start