GitHub Actions Notifications on Android

See the result of a trusted GitHub Actions job without repeatedly checking the run page.

QuickStart

Needs: GitHub repository with Actions secrets access

  1. Install QueueRing on Android, register the device, allow notifications, and copy the current Queue ID. Keep the phone online.
  2. Set repository secret QUEUERING_QUEUE_ID under Settings → Secrets and variables → Actions. Copy the YAML into .github/workflows/android-notification.yml on the default branch. Run it from Actions → Run workflow with fail_demo disabled; enable it for the failure test. The push trigger targets trusted main; change it to your trusted branch if needed.
  3. Verify on your device

Queue ID grants permission to send: keep it secret. If leaked, reissue it in the app and update your configuration. Security details.

202 only means accepted; display, history, and ID validity are not guaranteed. Resends can duplicate alerts; retry the notification only, never the job or agent. HTTP errors and retry advice.

Save as .github/workflows/android-notification.yml
name: Android notification example
on:
  workflow_dispatch:
    inputs:
      fail_demo:
        description: Test the failure notification
        type: boolean
        default: false
  push:
    branches: [main]
permissions: {}
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Demonstrate a job result
        shell: bash
        env:
          FAIL_DEMO: ${{ inputs.fail_demo }}
        run: |
          if [[ "$FAIL_DEMO" == true ]]; then exit 1; fi
          printf 'Demo completed\n'
  notify:
    needs: build
    if: ${{ !cancelled() && (needs.build.result == 'success' || needs.build.result == 'failure') }}
    runs-on: ubuntu-latest
    timeout-minutes: 2
    steps:
      - name: Send job result to Android
        shell: bash
        env:
          QUEUE_ID: ${{ secrets.QUEUERING_QUEUE_ID }}
          BUILD_RESULT: ${{ needs.build.result }}
        run: |
          set -eu
          set +x
          : "${QUEUE_ID:?Load your Queue ID before sending}"
          [[ "$QUEUE_ID" =~ ^qr1\.[A-Za-z0-9_-]{1,16}\.[A-Za-z0-9_-]{59}$ ]] || {
            printf 'Invalid Queue ID format; copy it from the app.\n' >&2
            exit 1
          }
          message="GitHub Actions build: $BUILD_RESULT"
          status=$(curl --silent --connect-timeout 10 --max-time 30 \
            --output /dev/null --dump-header /dev/stderr --write-out '%{http_code}' \
            --request POST "https://api.queuering.org/q/$QUEUE_ID" \
            --header 'Content-Type: text/plain; charset=utf-8' \
            --data-binary "$message") || {
            printf 'Notification transport failed; check connectivity.\n' >&2
            exit 1
          }
          printf 'Notification HTTP %s\n' "$status" >&2
          [[ "$status" == 202 ]]

Copy buttons copy code only. If unavailable, select the code and copy manually. Code messages are shared with the English examples.

Verify on your device

Run workflow_dispatch once with fail_demo disabled and once enabled. Expect build: success and build: failure on Android. The failed build stays failed; notification failure marks the separate notify job as failed. Open that job to inspect HTTP status and response headers without printing the Queue ID.

If HTTP 202 arrives without a phone alert, verify the current Queue ID, Android notification permission, and device connectivity first. Check the app's recent history as an additional observation, not a delivery receipt.

More examples and integration limits

Integration steps and limits

A repository where you can configure Actions secrets, GitHub-hosted Ubuntu runners, and curl/Bash (included on those runners).

The notify job depends on build and uses an explicit status condition, so it can run after a failed build. Skipped builds and canceled workflows are excluded; cancellation while sending can still interrupt it. This workflow deliberately has no pull_request or pull_request_target trigger. Do not expose the secret to untrusted PR code. Fork and Dependabot workflows generally do not receive repository secrets; reusable workflows need explicit secret passing. If using an environment secret instead, declare that environment on notify and satisfy its protection rules. See GitHub secrets documentation and status conditions.

Queue ID security details

Keep the Queue ID private

A Queue ID grants permission to send notifications to your device: treat it as a bearer credential. Never put a real ID in public repositories, Issues, logs, screenshots, prompts, or fixed sample values. Do not use shell tracing (set -x), curl verbose/trace output, or shared terminal recordings. The URL and process arguments can contain the ID; run only on a trusted machine and restrict proxy/access logs.

If exposed, reissue/rotate the Queue ID in the app and replace every local configuration or Actions secret using the old ID. Copy the new value from the app rather than editing the old one. A 202 response cannot confirm that an old ID remains valid.

API contract, HTTP errors, and retries

Understand the response and retry safely

Notifications and message history are best-effort. HTTP 202 means request acceptance, not guaranteed display on Android or guaranteed history storage. Invalid, inactive, and outdated Queue IDs intentionally receive the same 202 response. History stores up to 100 messages captured on the device; absence from history alone does not establish an HTTP failure.

The endpoint is POST https://api.queuering.org/q/{queueId}. Use UTF-8 text/plain; charset=utf-8, or application/json with a required nonempty body string and optional nonblank title. The raw request body, including JSON syntax, must be at most 4096 bytes.

HTTP responses and next steps
ResultWhat to do
202Check Android notification permissions, connectivity, and the current ID in the app. Do not retry merely because no notification appears.
400Fix the request, Content-Type, JSON syntax, and required fields before resending.
413Shorten the UTF-8 body to 4096 bytes or less; JSON escaping also counts.
429Wait at least Retry-After seconds when provided. If absent, use a delayed bounded retry with backoff rather than a tight loop.
503Temporarily unavailable. Inspect the failure and retry later with bounded backoff.
Transport failure / timeoutThe outcome may be unknown. Check connectivity before deciding to resend.

The samples send once, with a 10-second connect timeout and 30-second total timeout. They print HTTP status and response headers (including Retry-After), and do not automatically retry. Resending can create duplicate alerts; there is no idempotency key in these examples. Retry the notification only, never rerun a backup, deployment, or agent just to send its status. Keep your normal job logs and monitoring as the source of truth.

Try another recipe

All recipes · QueueRing home