> ## Documentation Index
> Fetch the complete documentation index at: https://docs.suprsend.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CI/CD with the SuprSend CLI

> Release SuprSend workflows, templates and other notification assets from a CI/CD pipeline with the SuprSend CLI, using GitHub Actions examples.

Use the SuprSend CLI in a pipeline to release notification changes the way you release code: keep workflows, templates and the rest as files in Git, review them in pull requests, and push them to production when you merge. The CLI authenticates with a service token, so no one has to sign in.

## Choose an approach

| Approach | How it works | Use it when |
| - | - | - |
| [Files in Git](#keep-your-assets-in-git) | Assets live as files in your repo. Pull requests are checked, and merging to `main` pushes them to production. | You want every change reviewed in a pull request, with Git as the record of what's live. |
| [Sync staging to production](#promote-staging-to-production-with-sync) | You build in the staging workspace in the dashboard, then a manual job copies everything to production. | Your team builds in the dashboard and wants to promote on demand, without keeping files in Git. |

## What you need

* A [service token](https://docs.suprsend.com/docs/developer/service-tokens.md), saved as a secret in your CI system, for example `SUPRSEND_SERVICE_TOKEN` in GitHub Actions.
* Node.js 18 or later on the runner, to run the CLI with `npx`. You can also [install the CLI](https://docs.suprsend.com/reference/cli-installation.md) another way.
* Your notification assets in the repo, in the folder layout the CLI uses (default `./suprsend/`).

> [!WARNING]
>
> Service tokens are account-wide: a token can act on every workspace in your account. Keep it in your CI system's secret store, and limit who can run jobs that use it, for example with GitHub environment protection rules.

## Keep your assets in Git

Pull the current live state from your staging workspace into the repo, then commit it:

```bash theme={"system"}
npx --yes suprsend category pull -w staging -F
npx --yes suprsend schema pull -w staging -F
npx --yes suprsend event pull -w staging -F
npx --yes suprsend template pull -w staging -F
npx --yes suprsend workflow pull -w staging -F
npx --yes suprsend translation pull -w staging -F
```

Each command writes to its own folder under `./suprsend/`, for example `./suprsend/workflows/<slug>/workflow.json`, and overwrites the files it pulls. `-F` uses the default folder without asking. Without it, a pull whose folder doesn't exist yet asks for one in a terminal, and fails with exit code 64 in a non-interactive shell. From then on, change the files in pull requests, or pull again after building in the dashboard.

## Release to production on merge

This GitHub Actions workflow pushes the assets in `./suprsend/` to production and commits them when changes reach `main`. It pushes them in the same order [`suprsend sync`](https://docs.suprsend.com/reference/cli-sync) uses, so categories and schemas exist before the templates and workflows that use them.

```yaml .github/workflows/suprsend-release.yml theme={"system"}
name: Release SuprSend notifications

on:
  push:
    branches: [main]
    paths: ["suprsend/**"]

# Run one release at a time, so an older commit can't overwrite a newer one.
concurrency:
  group: suprsend-production
  cancel-in-progress: false

jobs:
  release:
    runs-on: ubuntu-latest
    # Add required reviewers to this environment to approve each release
    # (private repositories need GitHub Enterprise for this).
    environment: production
    env:
      SUPRSEND_SERVICE_TOKEN: ${{ secrets.SUPRSEND_SERVICE_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Push and commit to production
        run: |
          npx --yes suprsend category push -w production --commit
          npx --yes suprsend schema push -w production --commit
          npx --yes suprsend event push -w production
          npx --yes suprsend template push -w production --commit
          npx --yes suprsend workflow push -w production --commit
          npx --yes suprsend translation push -w production --commit
```

### How a release behaves

* **`--commit` makes changes live.** With `--commit`, workflows, templates, schemas, categories and translations go live. Without it, they're saved as drafts, so someone can review and commit them in the dashboard.
* **Events and category translations go live without `--commit`.** Events have no draft state, so `event push` doesn't take `--commit`. `category push` sends preference-category translations live either way. If you leave `--commit` off, `event push` fails for events linked to schemas that aren't live yet.
* **Template variants commit together.** `template push --commit` commits all of a template's variants together, so one invalid variant fails the whole template with exit code 1. With `-F`, it commits only the variants that pass validation and skips the rest with a warning. Leave `-F` off in CI, as above, so a bad variant fails the job instead of being skipped.
* **A failed step stops the release, but isn't rolled back.** The step stops at the first command that fails, because GitHub Actions runs `run` scripts with `bash -e`. The asset types pushed before the failure, and the items that succeeded in the failing command, are already live. Fix the cause and re-run the job.
* **Roll back with Git.** To roll back a release, revert the commit on `main`. The release job then pushes and commits the previous files.
* **Keep only the asset types you use.** Remove the commands for asset types you don't store in the repo. A push fails with exit code 2 when its folder, for example `./suprsend/templates`, doesn't exist.

> [!NOTE]
>
> Pushes and pulls never delete anything:
>
> * **Deleted in the repo:** the asset stays in the workspace. Delete it in the dashboard too.
> * **Deleted in the workspace:** its folder stays in the repo after a pull. Delete the folder yourself.
> * **Renamed slug:** counts as a new asset. The old one stays until you delete it.

## Check files in pull requests

`--dry-run` reads and parses the local files without calling SuprSend, so it catches broken JSON before merge. A file that doesn't parse fails the check with exit code 2. The CLI still needs a token value to start, but a dry run never uses it, so a placeholder works and keeps your real token out of pull-request jobs.

```yaml .github/workflows/suprsend-check.yml theme={"system"}
name: Check SuprSend files

on:
  pull_request:
    paths: ["suprsend/**"]

jobs:
  check:
    runs-on: ubuntu-latest
    env:
      # --dry-run makes no API calls, but the CLI needs a token value to start.
      SUPRSEND_SERVICE_TOKEN: dry-run-placeholder
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Parse local files
        run: |
          npx --yes suprsend category push --dry-run
          npx --yes suprsend schema push --dry-run
          npx --yes suprsend event push --dry-run
          npx --yes suprsend template push --dry-run
          npx --yes suprsend workflow push --dry-run
          npx --yes suprsend translation push --dry-run
```

As in the release job, keep only the commands for asset types you store in the repo.

A dry run checks only local files: that they parse, and for schemas that `payload_schema.json` compiles as a JSON Schema. SuprSend validates the content itself when you push. `category push --dry-run` checks `categories.json` but doesn't read the category translation files, so a broken translation file is only caught by a real push.

## Promote staging to production with sync

If you build in the staging workspace instead of in files, promote everything at once with [`suprsend sync`](https://docs.suprsend.com/reference/cli-sync). Run it from a manual job so a person decides when to release:

```yaml .github/workflows/suprsend-promote.yml theme={"system"}
name: Promote SuprSend staging to production

on: workflow_dispatch

jobs:
  promote:
    runs-on: ubuntu-latest
    environment: production
    env:
      SUPRSEND_SERVICE_TOKEN: ${{ secrets.SUPRSEND_SERVICE_TOKEN }}
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Sync staging to production
        run: npx --yes suprsend sync --from staging --to production --dir ./sync-tmp
```

> [!WARNING]
>
> `sync` pushes **and commits** every synced asset, so the changes go live in production immediately.
>
> * **Local files are pushed too.** It also pushes workflows, schemas, events, templates and translations already in its local directory, not only what it pulled from staging. Use a fresh `--dir`, as above, so only assets from staging are pushed.
> * **Invalid template variants are skipped.** Unlike `template push --commit`, `sync` commits the valid variants and skips invalid ones with a warning, which CI logs hide. It still exits with 0 unless every changed variant of a template fails, so check the destination after a sync.

## Keep generated types in step

If your code uses [types generated](https://docs.suprsend.com/docs/type-generation.md) from your payload schemas, regenerate them in CI and fail the build when they're out of date:

```bash theme={"system"}
rm -f ./types/suprsend-types.ts
npx --yes suprsend generate-types typescript --workspace production --output-file ./types/suprsend-types.ts
git diff --exit-code -- ./types/suprsend-types.ts
```

`generate-types` reads the **live schemas in the workspace**, not the `payload_schema.json` files in your repo. So run this check after the release job on `main`, or on a schedule, not on pull requests:

* On a pull request, a schema change in the repo isn't live yet, so the check can't see it.
* A schema committed in the workspace by someone else would fail the check on unrelated pull requests.
* The check needs your real service token, which you keep out of pull-request jobs.

Point `--workspace` at the workspace your repo releases to, as above.

Deleting the file first matters: if generation fails, for example on an expired token, the command still exits with 0, and the missing file then shows up in `git diff`.

## How the CLI behaves in CI

* **No prompts.** Confirmation prompts appear only in an interactive terminal. In CI, `--commit`, `sync` and `workflow disable` run straight away, so check `-w` (`--to` for `sync`) before you run them.
* **Logs are hidden.** When `stderr` isn't a terminal, as in CI runners, the CLI hides its log lines and prints only command output and a JSON error. Rely on the exit code.
* **Re-runs are safe.** When there's nothing new to commit, the command exits with 0. In a terminal, it also logs "no changes to commit".
* **Self-hosted or BYOC.** Point the CLI at your deployment with the `SUPRSEND_BASE_URL` and `SUPRSEND_MGMNT_URL` environment variables, or use a [CLI profile](https://docs.suprsend.com/docs/self-hosted/cli-profiles.md).

| Exit code | Meaning |
| - | - |
| `0` | Success |
| `1` | General or API error, including a partial failure |
| `2` | Validation error, for example a file that doesn't parse |
| `3` | Authentication failed: a missing or invalid token, or a token without permission. When a push of several items gets an auth error from the API, it exits with `1` instead. |
| `4` | Resource not found |
| `5` | Reserved for rate limiting. CLI 1.1.0 reports a rate-limit error as `1`. |
| `64` | Invalid usage, such as a missing flag |

## Let a coding agent write the pipeline

If you use a coding agent such as Claude Code or Cursor, add the [`suprsend-cli` skill](https://github.com/suprsend/skills/tree/main/skills/suprsend-cli) so it uses the right commands and flags when it writes or changes these workflows.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.