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
OpenClaw Gateway running in Docker on a VPS (Hostinger)
gog CLI for Google OAuth + Calendar access
OpenClaw cron jobs for scheduling
WhatsApp channel via WhatsApp Web (https://whiskeysockets-baileys-94.mintlify.app/introduction)
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@
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
Personal calendar (owner): XXXXXXX@gmail.com
US Holidays (reader): en.usa#holiday@group.v.calendar.google.com
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)
Separate long-lived integrations into a dedicated GCP project. “What the heck is using this credential?” is real life.
Headless OAuth + containers: avoid localhost listener flows. Use remote/manual code exchange if available.
Cron runs aren’t your interactive shell: PATH, HOME, and keyring prompting will bite you.
In containers, file ownership is everything: if your runtime user is node, tokens owned by root will fail silently until you inspect permissions.
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