Home/Guides/Send a push notification from GitHub Actions
Guide · GitHub Actions

How to send a push notification from GitHub Actions

CI runs long enough to walk away, and email alerts are easy to miss. A Lert step buzzes your phone the moment a workflow finishes — green or red — so you're not refreshing the Actions tab. It's one curl step, one repo secret, no Marketplace action to trust and no API key to rotate. This guide covers notify-on-everything, notify-on-failure-only, and a note on matrix builds.

Step 1: store your Lert URL as a secret

Open the Lert app and copy your endpoint — something like https://lert.dev/p/your-id. Because anyone with this URL can send you notifications, don't paste it directly into a workflow file that lives in the repo. Instead, store it as an encrypted secret:

  1. In your repository, go to Settings → Secrets and variables → Actions.
  2. Click New repository secret.
  3. Name it LERT_URL and paste your endpoint as the value.

GitHub encrypts the value and masks it in logs, so even echo $LERT_URL shows up as *** in build output. Reference it in a workflow as ${{ secrets.LERT_URL }}.

Step 2: add a notify step

Add a final step to the job you care about. The key is if: always(), which tells Actions to run this step even if an earlier one failed — otherwise a failing build would skip your notification, which is exactly the run you most want to hear about. Pull the outcome from ${{ job.status }} so the message says whether it passed.

.github/workflows/ci.yml
name: CI
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: npm ci && npm test

      - name: Notify phone
        if: always()
        env:
          LERT_URL: ${{ secrets.LERT_URL }}
          STATUS: ${{ job.status }}
        run: |
          curl -X POST "$LERT_URL" \
            -H "Content-Type: application/json" \
            -d "{\"title\":\"CI $STATUS\",\"body\":\"${{ github.repository }} @ ${{ github.ref_name }}\",\"tag\":\"ci\"}"

Push a commit and watch it run. When the job ends, job.status resolves to success, failure, or cancelled, and that word lands on your lock screen alongside the repo and branch. A 200 with {"delivered":1} confirms it went through. Passing the secret through the env block keeps the URL out of the visible command line.

Why if: always() matters. By default a step is skipped once any earlier step in the job fails. Without always(), your notify step would never run on a broken build — so you'd only hear about the runs that went fine. always() forces it to run either way, and job.status tells you which it was.

Notify only when the build breaks

Green builds are the boring ones. If you only want a buzz when something is actually wrong, swap always() for failure(). That condition is true only when a previous step in the job failed, so successful runs stay silent.

failure-only step
      - name: Alert on failure
        if: failure()
        env:
          LERT_URL: ${{ secrets.LERT_URL }}
        run: |
          curl -X POST "$LERT_URL" \
            -H "Content-Type: text/plain" \
            --data-binary "❌ ${{ github.repository }} failed on ${{ github.ref_name }}"

Here we use text/plain with --data-binary so branch names or commit text with special characters don't need JSON escaping — the whole body becomes the notification text. This is the version most teams keep on their main branch: quiet until it isn't.

A note on matrix builds

If your job uses a strategy.matrix — say Node 18, 20, and 22 — a notify step inside that job runs once per matrix leg. On a broad matrix that's a lot of buzzes, and it can bump you toward the rate limit (60 requests/minute per IP, 50/day on the Free plan). Two ways to keep it to one notification:

  • Reference the leg in the message if you genuinely want per-leg alerts: include ${{ matrix.node }} in the body so each push says which version failed.
  • Move the notify to a dependent job that runs after the matrix. Give it needs: [test] and if: always(), and it fires exactly once with the overall result — cleaner for a wide matrix.
one notification after a matrix
  notify:
    needs: [test]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Notify phone
        env:
          LERT_URL: ${{ secrets.LERT_URL }}
          STATUS: ${{ needs.test.result }}
        run: |
          curl -X POST "$LERT_URL" \
            -d "{\"title\":\"CI $STATUS\",\"body\":\"${{ github.repository }} matrix done\"}"

Note the dependent job reads needs.test.result rather than job.status, because it's reporting on the upstream job's outcome, not its own.

Beyond CI

The same one-step pattern works for any workflow: a scheduled cron job, a release pipeline, a nightly data export. Anywhere a runner can execute curl, it can buzz your phone. For deploy-specific wording, see deploy notifications; for the raw request, the curl guide.

Why Lert for GitHub Actions

Most CI notification setups mean adding a Marketplace action, granting it permissions, and storing a webhook or API token you then have to trust and maintain. Lert is a single curl against a secret you control: the endpoint URL is the authentication, nothing runs in your pipeline that you didn't write, and you can regenerate the URL any time. It's the least-privilege way to get your phone to tell you how the build went.

FAQ

How do I keep my Lert URL out of the workflow file?

Store it as an encrypted Actions secret named LERT_URL in the repository settings, then reference it as ${{ secrets.LERT_URL }}. GitHub masks secrets in logs, so the URL never appears in build output.

How do I notify on both success and failure?

Add if: always() to the notify step so it runs regardless of earlier failures, and include ${{ job.status }} in the message so the notification tells you whether the run passed or failed.

Can I notify only when the workflow fails?

Yes. Use if: failure() on the step instead of if: always(). It runs only when a previous step in the job failed, so you hear about red builds and stay quiet on green ones.

Do I need an API key or a Marketplace action?

No. Your Lert endpoint URL is the authentication, and a single curl sends the notification — no key to manage and no third-party action to add.

Stop refreshing the Actions tab.

Download Lert, add one secret and one step, and let your phone report the build.

Get the app iOS