docs/.mintlify/skills/framework-integration/references/deployment.md
@novu/framework operates in a GitOps model — your workflows live in source control, and a sync step pushes them to Novu Cloud after each merge.
1. Develop locally (Studio against your machine)
2. Open PR
3. CI runs `npx novu sync` against Development env
4. Test e2e in Development
5. Merge to main
6. CI runs `npx novu sync` against Production env
Each Novu environment (Development, Production) has its own secret key. You sync once per environment.
| Environment | Purpose | Secret key |
|---|---|---|
| Local Studio | Develop against your machine via tunnel | (uses dev secret key) |
| Development | Staging — non-technical peers preview controls | NOVU_SECRET_KEY (dev) |
| Production | Customer-facing triggers | NOVU_SECRET_KEY (prod) |
Get keys from https://dashboard.novu.co/api-keys for each environment.
npx novu@latest sync \
--bridge-url <YOUR_DEPLOYED_URL_WITH_BRIDGE_ENDPOINT> \
--secret-key <NOVU_SECRET_KEY> \
--api-url https://api.novu.co
For EU customers:
npx novu@latest sync \
--bridge-url <YOUR_DEPLOYED_URL_WITH_BRIDGE_ENDPOINT> \
--secret-key <NOVU_SECRET_KEY> \
--api-url https://eu.api.novu.co
The bridge URL is stored so Novu Cloud knows where to fetch step content at trigger time.
Free-tier preview URLs are deployment-protected by default. Enable Protection Bypass for Automation in your Vercel project settings, then pass the bypass token in the bridge URL:
npx novu@latest sync \
--bridge-url "https://my-app-preview.vercel.app/api/novu?x-vercel-protection-bypass=$BYPASS_TOKEN" \
--secret-key $NOVU_SECRET_KEY
Use the novuhq/actions-novu-sync@v2 action.
mainname: Sync workflows to Novu Production
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Sync state to Novu
uses: novuhq/actions-novu-sync@v2
with:
secret-key: ${{ secrets.NOVU_SECRET_KEY }}
bridge-url: ${{ secrets.NOVU_BRIDGE_URL }}
api-url: https://api.novu.co
name: Sync workflows to Novu Development
on:
pull_request:
branches: [main]
jobs:
deploy-dev:
runs-on: ubuntu-latest
steps:
- name: Sync state to Novu (Dev)
uses: novuhq/actions-novu-sync@v2
with:
secret-key: ${{ secrets.NOVU_DEV_SECRET_KEY }}
bridge-url: ${{ secrets.NOVU_DEV_BRIDGE_URL }}
api-url: https://api.novu.co
| Input | Required | Default | Description |
|---|---|---|---|
secret-key | Yes | — | NOVU_SECRET_KEY for the target environment |
bridge-url | Yes | — | Public URL of the deployed bridge (https://app.com/api/novu) |
api-url | No | https://api.novu.co | Use https://eu.api.novu.co for EU |
All other CI tools can use the CLI directly. Examples:
sync_novu:
image: node:20
script:
- npx novu@latest sync --bridge-url $NOVU_BRIDGE_URL --secret-key $NOVU_SECRET_KEY
only:
- main
version: 2.1
jobs:
sync-novu:
docker:
- image: cimg/node:20.11
steps:
- run: npx novu@latest sync --bridge-url $NOVU_BRIDGE_URL --secret-key $NOVU_SECRET_KEY
workflows:
deploy:
jobs:
- sync-novu:
filters:
branches:
only: main
pipeline {
agent any
stages {
stage('Sync Novu') {
when { branch 'main' }
steps {
sh 'npx novu@latest sync --bridge-url $NOVU_BRIDGE_URL --secret-key $NOVU_SECRET_KEY'
}
}
}
}
pipelines:
branches:
main:
- step:
image: node:20
script:
- npx novu@latest sync --bridge-url $NOVU_BRIDGE_URL --secret-key $NOVU_SECRET_KEY
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: NodeTool@0
inputs:
versionSpec: "20.x"
- script: npx novu@latest sync --bridge-url $(NOVU_BRIDGE_URL) --secret-key $(NOVU_SECRET_KEY)
displayName: "Sync Novu"
language: node_js
node_js:
- "20"
deploy:
provider: script
script: npx novu@latest sync --bridge-url $NOVU_BRIDGE_URL --secret-key $NOVU_SECRET_KEY
on:
branch: main
┌──────────────────────────────────────────────────────────────────┐
│ PR opened / updated │
│ 1. Build & test app │
│ 2. Deploy preview to Vercel/Render │
│ 3. `npx novu sync --bridge-url <preview> --secret-key DEV` │
│ 4. Run e2e tests against Dev environment │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ PR merged to main │
│ 1. Deploy app to production │
│ 2. `npx novu sync --bridge-url <prod> --secret-key PROD` │
└──────────────────────────────────────────────────────────────────┘
/api/novu will block Novu — exempt that path.You're syncing with the wrong environment's key. Each Novu environment has its own key — don't mix Dev and Prod.
GET /api/novu — Novu fetches the registration list from there.workflowId is not auto-deleted. Delete it manually in the Dashboard if needed.NOVU_SECRET_KEY is required even for the bridge.