top of page

Building a WhatsApp Daily Briefing Agent (OpenClaw) - Lessons From the Trenches

  • Ken Munson
  • Mar 31
  • 4 min read

I spent a long couple of sessions turning a “wouldn’t it be nice…” idea into an actual working pipeline: a daily briefing agent that can pull my personal Google Calendar (plus US holidays) and deliver a summary to my WhatsApp on a schedule. Of course, that is not my main Google account nor my main phone number in use for this.

This is one of those projects where the idea is simple, and the reality is OAuth, containers, file permissions, corn runtimes, and messaging quirks, blah blah blah.  I won't use the word nightmare, but it was a REAL challenge. If OpenClaw sound like, oh, that would be fun to play with. Its not. But it can be useful and rewarding and maybe, eventually, a little fun.


Goal

  • Use a sandboxed dedicated Google account: XXXXXXXX@gmail.com

  • Pull events from:

  • my the calendar for this account (XXXXXXXX@gmail.com)

  • the US holidays calendar (en.usa#holiday@group.v.calendar.google.com)

  • Deliver a daily briefing via WhatsApp on a predictable schedule:

  • Weekdays: 9:00 AM

  • Weekends: 10:00 AM

  • Keep it minimal at first (calendar only), then later add weather and market closes, etc.


The stack

 

What broke (and why)

1) “deleted_client” OAuth error

When I first tried to authorize Google, Chrome blocked the request with:

  • Error 4021: deleted_client

Root cause: the OAuth client ID in the JSON file referenced a Google Cloud OAuth client that had been deleted.

Fix: create a clean dedicated GCP project openclaw-XXXXXXX, enable the needed APIs, create a new OAuth client, download the new JSON, and replace the old one in the container.

2) Localhost callback + Docker: the classic headless OAuth trap

The normal flow uses a redirect like:

But 127.0.0.1 inside a container is not the same as 127.0.0.1 on the host, and certainly not my laptop. I tried SSH tunnels, but the listener was bound to container localhost (0100007F in /proc/net/tcp).

Fix: use gog’s remote/manual OAuth flow:

  • step 1 prints the URL

  • I complete consent in a browser

  • step 2 exchanges the returned redirect URL (with code=) without needing a local callback server

That avoided all the loopback networking complexity.

3) OAuth Consent Screen “testing” restriction (403 access_denied)

In a new project, the app is “in testing” and Google blocks access unless the account is an approved tester.

Fix: add XXXXXXXX@gmail.com as a Test user in the consent screen settings (new UI puts this under Audience).

4) Cron is non-interactive: keyring passphrase prompts break automation

Interactive gog commands prompted for the keyring passphrase. Cron runs can’t answer prompts, so the briefing couldn’t unlock the token.

Fix: provide the passphrase non-interactively via container env:

  • set GOG_KEYRING_PASSWORD in the OpenClaw container runtime

  • keep it out of the compose file directly by using a root-owned env file with restrictive permissions

5) “permission denied” reading token (root vs node ownership mismatch)

The OpenClaw gateway and cron runs were executing as user node, but my token files ended up owned by root:root:

  • /data/.config/gogcli/keyring/token:default:XXXXXXX@

    .com

Fix: chown the specific token files to node:node while keeping permissions tight (600).

6) WhatsApp UI gotcha: Archive isn’t an “inbox”

I archived the chat thread thinking it would help me see new messages. WhatsApp will keep updating timestamps in Archived and can optionally keep chats archived even when new messages arrive.

Fix: disable Keep chats archived so any new message pops back into the main chat list. Also: accept that WhatsApp is “thread based,” not an inbox.

 

The working configuration (high-level)

Cron schedule

Two persistent cron jobs:

  • Weekday job: 15 9 1-5 with TZ America/Chicago

  • Weekend job: 15 10 0,6 with TZ America/Chicago

Delivery:

  • WhatsApp to +1469XXXXXXX

Calendar window semantics

“Today + next 3 days” in US Central (America/Chicago), not UTC.

That matters because “today” is a local human concept.

Included calendars

 

Key commands and patterns (sanitized)


List calendars

gog calendar calendars --account XXXXXXX@gmail.com


Remote OAuth flow (the lifesaver)

gog auth add XXXXXXX@gmail.com \  --services gmail,calendar,drive,docs,sheets,contacts,people \  --remote --step 1 --timeout 10m --force-consent

Then after approving in browser, copy the final redirect URL (contains code=) and:

gog auth add XXXXXXXX@gmail.com \  --services gmail,calendar,drive,docs,sheets,contacts,people \  --remote --step 2 --force-consent \  --auth-url '<PASTE_REDIRECT_URL>'

Verify:

gog auth list


Fix token ownership for node runtime

chown node:node /data/.config/gogcli/keyring/token:XXXXXXX@gmail.com \               /data/.config/gogcli/keyring/token:default:XXXXXXX@gmail.com


One-shot cron test job (great for debugging delivery)

openclaw cron add \  --name "Daily briefing test (one-shot)" \  --at "1m" \  --session isolated \  --message "Daily briefing. Calendar window: today + next 3 days (America/Chicago). Calendars:XXXXXXXX@gmail.com and en.usa#holiday@group.v.calendar.google.com. If empty, say so clearly." \  --announce \  --channel whatsapp \  --to "+1469XXXXXXX" \  --delete-after-run

 

Lessons learned (the parts worth remembering)

  1. Separate long-lived integrations into a dedicated GCP project. “What the heck is using this credential?” is real life.

  2. Headless OAuth + containers: avoid localhost listener flows. Use remote/manual code exchange if available.

  3. Cron runs aren’t your interactive shell: PATH, HOME, and keyring prompting will bite you.

  4. In containers, file ownership is everything: if your runtime user is node, tokens owned by root will fail silently until you inspect permissions.

  5. WhatsApp is thread-first: archiving changes how you see messages, not whether they arrive.

 

Next upgrades

Now that the plumbing is stable, the fun stuff can start:

  • Weather (daily, location-based)

  • Market closes (S&P 500 / Dow / Nasdaq — previous close)

  • “Top 3 personal priorities” prompt

  • More calendar hygiene (recurring reminders, admin blocks, etc.)

Comments


bottom of page