Daily GA4 pulse digests, delivered. Email, webhook, or markdown — no service-account dance.
ga4-pulse turns your Google Analytics 4 property into a daily one-page digest delivered wherever you read mail or chat. Bring your own GA4 property + sender (Mailgun, Resend, SendGrid, SMTP, Slack). Skip the service-account-on-property-binding pain — OAuth refresh token only.
- One-page pulse: sessions, users, top pages, traffic sources, conversion funnel, followups
- Multi-sender: Mailgun (EU/US), Resend, SendGrid, generic SMTP, Slack webhook
- OAuth refresh token auth — no GA4 admin UI dance, no sensitive scope verification
- GitHub Action:
uses: HackrsValv/ga4-pulse@v1in any workflow - Template-repo ready:
gh repo create --template HackrsValv/ga4-pulse-template ga4-pulse setup: drops a working workflow + config into any existing repo
# 1. One-time OAuth flow (browser opens)
npx @hackrsvalv/ga4-pulse auth
# 2. Scaffold workflow + config into your repo
npx @hackrsvalv/ga4-pulse setup
# 3. Add GitHub secrets (CLI prints exactly what to paste)
# GA4_OAUTH_CLIENT_ID, GA4_OAUTH_CLIENT_SECRET, GA4_OAUTH_REFRESH_TOKEN
# plus sender-specific (e.g. MAILGUN_API_KEY)
# 4. Smoke-test
gh workflow run pulse-email.yml -f dry_run=trueFirst scheduled fire lands next morning per the cron in .github/workflows/pulse-email.yml.
ga4-pulse auth # OAuth refresh-token flow
ga4-pulse send # query GA4 + render + send
ga4-pulse dry-run # write pulse-report.{html,md} to CWD; no send
ga4-pulse setup # scaffold workflow + pulse.config.yaml in CWD
ga4-pulse --version- uses: HackrsValv/ga4-pulse@v1
with:
property-id: '533218728'
hostname-regex: 'mysite\.com$'
window: 24h
sender: mailgun
from: 'pulse@mysite.com'
to: 'me@example.com'
timezone: 'Europe/Vilnius'
env:
GA4_OAUTH_CLIENT_ID: ${{ secrets.GA4_OAUTH_CLIENT_ID }}
GA4_OAUTH_CLIENT_SECRET: ${{ secrets.GA4_OAUTH_CLIENT_SECRET }}
GA4_OAUTH_REFRESH_TOKEN: ${{ secrets.GA4_OAUTH_REFRESH_TOKEN }}
MAILGUN_API_KEY: ${{ secrets.MAILGUN_API_KEY }}
MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}
MAILGUN_REGION: euFull input reference in docs/action-reference.md.
- Quickstart — 10-step setup, zero to first email
- OAuth setup (GA4) — Google Cloud Console walkthrough
- OpenPanel source — alternative analytics upstream (experimental)
- Providers — sender-specific configuration
- Config reference —
pulse.config.yamlschema - Troubleshooting — common errors and fixes
GA4 admin UI rejects *.iam.gserviceaccount.com emails as users since 2025. The analytics.manage.users scope needed to bind service accounts via API is gated behind app verification for non-Google OAuth clients. Refresh token from your own user (Testing-mode consent screen, no verification needed) sidesteps the entire mess.
Tradeoff: refresh token has the same access as the user. Don't share it. Rotate via ga4-pulse auth if compromised.
AGPL-3.0-or-later. If you run a modified version as a service, share your changes.
Extracted from the vaikutreneris.lt daily pulse pipeline. Built to scratch our own itch — running it on your own property is a one-shot setup.