CI/CD Notifications

Know the moment your build passes, fails, or needs approval — right on your screen.

The notification gap in CI/CD

You push a commit and the pipeline starts. Three minutes later GitHub Actions finishes. An email lands in your inbox — eventually. You already checked Slack twice. You refreshed the PR page once. By the time you see the green checkmark, you have lost your context and wasted the minutes in between.

For deploy gates the problem is worse. You want a human approval step before shipping to production, but the options are either a GitHub Environments manual review (which sends another email) or a Slack message that gets buried. There is no way to surface a “Ship it?” prompt directly on your screen and block the pipeline until you respond.

syncfu fills this gap. It runs as a tiny server on your developer machine, receives HTTP calls from your CI pipeline via a webhook tunnel, and renders a persistent, actionable overlay right where you are working.

How syncfu helps

  • Instant build alerts. A final workflow step POSTs the build result to syncfu. Pass or fail, the overlay appears on your screen within a second — with the branch name, commit message, and a direct link to the run.
  • Deploy approval gates. The pipeline pauses and syncfu shows a “Deploy to production?” overlay with Ship and Cancel buttons. The script blocks on the HTTP wait endpoint until you click. No manual review pages, no inbox hunting.
  • Multi-stage progress. Send a notification at the start of the pipeline and update its progress value as each stage completes. A single persistent overlay shows build → test → deploy progress without creating notification spam.

GitHub Actions — build status alerts

Expose syncfu on your developer machine using ngrok or Cloudflare Tunnel, then store the tunnel URL as a repository secret (SYNCFU_WEBHOOK_URL). Add a notification step at the end of your workflow:

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Notify — build passed
        if: success()
        run: |
          curl -s -X POST "${{ secrets.SYNCFU_WEBHOOK_URL }}/notify" \
            -H "Content-Type: application/json" \
            -d @- <<JSON
          {
            "title": "Build passed",
            "body": "${{ github.ref_name }} — ${{ github.event.head_commit.message }}",
            "priority": "normal",
            "actions": [{ "id": "view", "label": "View run", "style": "primary", "url": "$RUN_URL" }]
          }
          JSON

      - name: Notify — build failed
        if: failure()
        run: |
          curl -s -X POST "${{ secrets.SYNCFU_WEBHOOK_URL }}/notify" \
            -H "Content-Type: application/json" \
            -d @- <<JSON
          {
            "title": "Build failed",
            "body": "${{ github.ref_name }} — ${{ github.event.head_commit.message }}",
            "priority": "high",
            "actions": [{ "id": "logs", "label": "View logs", "style": "danger", "url": "$RUN_URL" }]
          }
          JSON

Deploy approval gates

For production deploys, replace the fire-and-forget curl with a blocking wait loop. The workflow step POSTs the notification, then polls the wait endpoint until you click Approve or Cancel on your desktop:

# deploy-gate.sh — runs on your developer machine as a pre-deploy hook,
# or from a self-hosted Actions runner on your local network.

#!/usr/bin/env bash
set -euo pipefail

BUILD_ID="${1:-unknown}"

echo "Sending deploy approval request…"
NOTIF_ID=$(syncfu send \
  -t "Deploy to production?" \
  -p critical \
  --action "ship:Ship it:success" \
  --action "cancel:Cancel:danger" \
  "Build ${BUILD_ID} passed all checks. Ready to ship." \
  --json | jq -r .id)

echo "Waiting for your response (notification ID: ${NOTIF_ID})…"

# --wait blocks until the overlay is dismissed
syncfu wait "${NOTIF_ID}"
DECISION=$?

if [ "${DECISION}" -eq 0 ]; then
  echo "Approved. Deploying build ${BUILD_ID}…"
  ./scripts/deploy.sh "${BUILD_ID}"
else
  echo "Deploy cancelled by user."
  exit 1
fi

You can also call the wait endpoint from any language without the CLI:

# Python deploy script with syncfu HITL gate
import requests
import sys

def deploy_with_approval(build_id: str) -> None:
    # Send the approval request
    resp = requests.post("http://localhost:9868/notify", json={
        "title": "Deploy to production?",
        "body": f"Build {build_id} passed all checks.",
        "priority": "critical",
        "actions": [
            {"id": "ship",   "label": "Ship it", "style": "success"},
            {"id": "cancel", "label": "Cancel",  "style": "danger"},
        ],
    })
    resp.raise_for_status()
    notif_id = resp.json()["id"]

    # Block until the user responds
    result = requests.get(f"http://localhost:9868/notify/{notif_id}/wait")
    result.raise_for_status()
    action_id = result.json()["actionId"]

    if action_id == "ship":
        print(f"Approved. Deploying {build_id}…")
        trigger_deploy(build_id)
    else:
        print("Deploy cancelled.")
        sys.exit(1)

Multi-stage pipeline progress

Instead of a separate notification for each stage, update a single overlay as the pipeline advances. This avoids notification clutter while keeping you informed:

#!/usr/bin/env bash
# pipeline-with-progress.sh

# Create the initial notification and capture its ID
NOTIF_ID=$(syncfu send \
  -t "Pipeline running…" \
  -p normal \
  --progress 0 \
  "Starting build stage" \
  --json | jq -r .id)

# --- Build stage ---
run_build
curl -s -X POST "http://localhost:9868/notify/${NOTIF_ID}/update" \
  -H "Content-Type: application/json" \
  -d '{"progress": 33, "body": "Build complete. Running tests…"}'

# --- Test stage ---
run_tests
curl -s -X POST "http://localhost:9868/notify/${NOTIF_ID}/update" \
  -H "Content-Type: application/json" \
  -d '{"progress": 66, "body": "Tests passed. Deploying…"}'

# --- Deploy stage ---
run_deploy
curl -s -X POST "http://localhost:9868/notify/${NOTIF_ID}/update" \
  -H "Content-Type: application/json" \
  -d '{
    "progress": 100,
    "title": "Pipeline complete",
    "body": "All stages passed. Deployed to staging.",
    "actions": [{"id": "open", "label": "Open staging", "style": "primary"}]
  }'

SSL/certificate expiry monitoring

Combine a cron job with syncfu to surface cert expiry warnings directly on your desktop before they become incidents:

#!/usr/bin/env bash
# check-certs.sh — run daily via cron

DOMAINS=("syncfu.dev" "api.syncfu.dev" "docs.syncfu.dev")
WARNING_DAYS=30

for DOMAIN in "${DOMAINS[@]}"; do
  EXPIRY=$(echo | openssl s_client -servername "${DOMAIN}" \
    -connect "${DOMAIN}:443" 2>/dev/null \
    | openssl x509 -noout -enddate 2>/dev/null \
    | cut -d= -f2)

  if [ -z "${EXPIRY}" ]; then
    syncfu send -t "Cert check failed" -p high "Could not retrieve cert for ${DOMAIN}"
    continue
  fi

  EXPIRY_EPOCH=$(date -d "${EXPIRY}" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "${EXPIRY}" +%s)
  NOW_EPOCH=$(date +%s)
  DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

  if [ "${DAYS_LEFT}" -le "${WARNING_DAYS}" ]; then
    PRIORITY="high"
    [ "${DAYS_LEFT}" -le 7 ] && PRIORITY="critical"

    syncfu send \
      -t "Cert expiring: ${DOMAIN}" \
      -p "${PRIORITY}" \
      --action "renew:Renew now:danger" \
      "${DOMAIN} expires in ${DAYS_LEFT} days (${EXPIRY})"
  fi
done

Build alerts

Pass or fail, know the moment your pipeline finishes. A persistent overlay with the branch name, commit, and a direct link to the run — no inbox required.

Deploy gates

Block the pipeline on a “Ship it?” overlay with Approve and Cancel buttons. The script waits for your click before continuing — works from any CI provider.

Pipeline progress

Update a single notification with a progress value as each stage completes. One overlay, no spam, full visibility into build → test → deploy.

Cert monitoring

A daily cron script checks TLS expiry dates and fires a high-priority desktop alert when renewal is approaching — before the outage, not during it.

Frequently asked questions

How do I get a desktop notification when a GitHub Actions build finishes?

Run syncfu on your developer machine and expose it via a tunnel (ngrok, Cloudflare Tunnel) or a webhook relay. In your workflow, add a final step that POSTs to your syncfu HTTP endpoint with the build status. The overlay appears on your screen within a second of the job completing.

Can syncfu block a deploy until I manually approve it?

Yes. Call syncfu send --wait with Approve and Cancel action buttons. The CLI blocks until you click one. In a pipeline script, check the exit code: 0 means approved, 1 means cancelled. This works for any stage gate — staging deploys, production releases, database migrations.

Does this work with Jenkins, GitLab CI, or CircleCI?

Any CI system that can run a curl command or shell script can trigger syncfu. Add a post-build step that POSTs to the syncfu HTTP API on your developer machine. The same tunnel approach used for GitHub Actions works for any hosted CI provider.

What if I am not at my desk when the build finishes?

syncfu is a desktop overlay — it is for when you are actively at your machine. For mobile or away-from-desk alerting, pair it with ntfy or Slack for the push side. A common setup: CI posts to both ntfy (phone alert) and syncfu (desktop overlay when you return).

Ship faster with fewer missed builds

Open source, no account needed. Install syncfu in 30 seconds and connect it to your first pipeline today.

Get started →