Home/Use cases/GitHub Actions notifications
Use case · GitHub Actions

Get a push notification when a GitHub Actions workflow finishes

A CI run is the classic dead-time gap: too short to context-switch, too long to sit and watch the logs scroll. Add one final step to your workflow and Lert buzzes your phone the moment it ends — green or red — with the repo name and status right on your lock screen.

The one-step version

Lert gives you a unique URL. Any step that can run curl can POST to it, and your phone gets a notification. To be told when a workflow finishes, you add a single step at the end that runs no matter what happened before it:

.github/workflows/ci.yml
# Runs on success AND failure thanks to always()
- name: Notify phone
  if: always()
  run: |
    curl -X POST "$LERT_URL" \
      -H "Content-Type: application/json" \
      -d "{\"title\":\"CI ${{ job.status }}\",\"body\":\"${{ github.repository }} @ ${{ github.ref_name }}\",\"tag\":\"github-actions\"}"
  env:
    LERT_URL: ${{ secrets.LERT_URL }}

That's the whole integration. ${{ job.status }} resolves to success, failure, or cancelled, so the title tells you the outcome at a glance. ${{ github.repository }} and ${{ github.ref_name }} tell you which repo and branch, which matters once more than one pipeline is pinging you.

Set it up in three steps

1. Copy your Lert URL

Open the Lert app and your unique endpoint is on screen — something like https://lert.dev/p/your-id. Tap Copy. Treat it like a password: anyone who has it can send you a notification, and you can regenerate it any time if it leaks.

2. Store it as a repository secret

Don't paste the raw URL into your YAML — it would show up in plain text in your repository and in fork PRs. Instead, go to Settings → Secrets and variables → Actions → New repository secret, name it LERT_URL, and paste your endpoint as the value. GitHub masks secret values in workflow logs automatically, so even the notify step won't leak it.

3. Add the notify step with if: always()

Append the step above as the last item in your job. The key detail is if: always(): without it, a failed earlier step would skip the notification and you'd never hear about the build that actually needed you. With it, the step runs on every outcome and job.status carries the result.

Only care about failures? Swap if: always() for if: failure() and the step fires only on red builds — a good place to start before you add success pings. The same one-line POST also works from a plain curl, a Python step, or a deploy pipeline.

Notify only the important jobs

You rarely want a buzz for every push. A few patterns keep it useful:

  • Failures only: if: failure() on the notify step — silence when green, a ping when you're needed.
  • Main branch only: wrap it in if: always() && github.ref == 'refs/heads/main' so feature-branch churn stays quiet.
  • Release workflows: put the step in your tag or release pipeline, where a failure genuinely blocks a ship.
  • Matrix jobs: add the step once per matrix leg, or use a final needs: job that reports the aggregate result to avoid a flood of pings.

A dedicated summary job

For a multi-job workflow, a single trailing job that depends on the rest gives you exactly one notification with the overall result:

.github/workflows/ci.yml
  notify:
    needs: [test, build]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - run: |
          STATUS="${{ needs.test.result }}/${{ needs.build.result }}"
          curl -X POST "$LERT_URL" \
            -d "{\"title\":\"CI done\",\"body\":\"${{ github.repository }} — test/build: $STATUS\"}"
        env:
          LERT_URL: ${{ secrets.LERT_URL }}

Because the notify job lists the others in needs and uses if: always(), it waits for them and then reports whether each one passed — one clean push instead of several.

Why Lert for CI alerts

Email notifications from CI get filtered and ignored; chat integrations need a bot, a webhook, and a channel nobody watches. Lert is a real iOS push that wakes a locked phone and buzzes a paired Apple Watch, set up with one step and one secret. The endpoint URL is the authentication, so there's no key to rotate and no app to install on a build runner. It's the shortest path from "did that pass?" to knowing.

FAQ

Why use if: always() instead of success()?

Because success() skips the step when an earlier step failed — exactly the builds you most want to hear about. if: always() runs the notify step on every outcome, and you read ${{ job.status }} inside the message to tell pass from fail.

Will my Lert URL show up in the workflow logs?

No, as long as you store it as a repository secret and reference it as ${{ secrets.LERT_URL }}. GitHub masks secret values in logs automatically, including in the notify step's command.

Can I notify only on failure to cut noise?

Yes. Use if: failure() on the notify step so it only fires when the workflow fails. It's a sensible default before you add success pings.

Do I need an API key or account for this?

No. Your Lert endpoint URL is the authentication — there's no separate key, token, or login to wire up.

How fast does the notification arrive?

Typically within a second or two of the step running. It's a real push, so it wakes a locked phone and buzzes a paired Apple Watch.

Stop refreshing the Actions tab.

Download Lert, store your URL as a secret, add one final step. Your phone tells you how the run went.

Get the app iOS