Every morning, somebody on the Velocity Sellers leadership team was spending 30-40 minutes reading Amazon and AI news before client calls. Reading Marketplace Pulse, skimming AboutAmazon, checking what dropped overnight in the model releases. It was necessary. It was also manual, inconsistent, and completely replicable by a machine.
I built an automated briefing system in an afternoon. It's been running every morning since April. The Velocity Sellers team gets a formatted 10-story HTML email at 7am โ 5 ecommerce stories, 5 AI/tech stories โ each with a "why it matters" paragraph written for an operator managing $100K+/month brands.
Total cost: $5-7/month in API calls.
Here's the build log.
The Problem, With Numbers
When you're running an agency, staying current isn't optional. Client calls start with "did you see what Amazon announced?" and if your answer is no, you lose credibility fast. Before this automation, the VS team was piecing together news from newsletters, Twitter, and random Slack shares โ decentralized, inconsistent, with no shared context.
The manual version looked like this:
- 30-40 minutes of daily reading across 6-8 sources
- No permanent record of what was shared or when
- Different team members finding different stories, no unified briefing
- Zero synthesis โ just raw links with no "here's why this matters to a brand running PPC on Amazon"
At 35 minutes/day, 5 days/week, that's 2.9 hours/week in senior team attention. Multiply that across 4 people who were each doing partial versions of this, and you're looking at 8-10 hours/week of news-reading that could be compressed into one 15-minute read of a pre-synthesized email.
The automation needed to: find the stories, understand which ones mattered for the specific audience (not generic AI news โ news with operator implications), write the "why it matters" in plain language, format it as a readable HTML email, and send it without anyone touching a keyboard.
The Tool Stack
- Claude Code CLI (headless mode) โ the intelligence layer. Runs web searches, synthesizes stories, writes the email body.
- launchd (macOS) โ the scheduler. Fires the script every morning at 7am, logs output.
- gws CLI โ Google Workspace CLI for sending Gmail via the command line. No OAuth prompts, no MCP server dependency.
- Python โ thin wrapper for email construction and the
gwscommand call. - A plain-text history file at
~/scripts/briefing-history.txtfor deduplication.
No external APIs beyond Claude. No new SaaS subscriptions. The whole thing fits in a 60-line shell script and a Python send function.
The Build
The shell script (daily-briefing.sh) is the orchestrator. Here's what it does:
#!/bin/bash
set -euo pipefail
LOG_DIR="$HOME/scripts/logs"
HISTORY_FILE="$HOME/scripts/briefing-history.txt"
LOG_FILE="$LOG_DIR/daily-briefing-$(date +%Y-%m-%d).log"
exec > "$LOG_FILE" 2>&1
# Load last 70 headlines to prevent repeats
RECENT_HEADLINES=$(tail -70 "$HISTORY_FILE" 2>/dev/null || echo "None yet")
PROMPT="You are producing a daily news briefing for the Velocity Sellers team...
IMPORTANT โ DEDUPLICATION: Do NOT include any of these stories:
--- PREVIOUSLY SENT ---
$RECENT_HEADLINES
--- END PREVIOUSLY SENT ---
Steps:
1. Use web search to find 5 important ecommerce stories from the last 24 hours...
2. Use web search to find 5 important AI/tech stories from the last 24 hours...
3. Compose a professional HTML email with inline CSS...
4. Write the 10 headlines to $HISTORY_FILE (append, do not overwrite)...
5. Send the email using a Python script that calls gws CLI..."
printf '%s' "$PROMPT" | claude \
-p \
--dangerously-skip-permissions \
--allowedTools "WebSearch Bash Read Write"
# Trim history to 70 entries (~1 week)
tail -70 "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
The critical flag is --dangerously-skip-permissions. In headless mode, Claude Code normally prompts for tool confirmation. That prompt will never be answered in a launchd job. The flag bypasses it. The allowed tools are scoped to exactly what's needed: WebSearch Bash Read Write. Nothing else.
The prompt gives Claude four jobs: search, synthesize, format, and send. The send step is the interesting one.
The Email Send Pattern
Inside the prompt, Claude generates a Python script that constructs an HTML MIMEText message and sends it via gws:
import base64, json, subprocess
from email.mime.text import MIMEText
msg = MIMEText(html_body, "html")
msg["To"] = "john.aspinall@velocitysellers.com, pete@velocitysellers.com, jake@velocitysellers.com"
msg["Subject"] = subject_line
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode("utf-8")
subprocess.run(
["gws", "gmail", "users", "messages", "send",
"--params", json.dumps({"userId": "me"}),
"--json", json.dumps({"raw": raw})],
check=True
)
Claude writes this Python to disk, executes it, and the email lands. The gws CLI uses the same OAuth credentials as the Google Workspace account โ already authenticated, no prompts, no token refresh dance.
The launchd Plist
The script fires via launchd, macOS's equivalent of cron (but more reliable):
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>7</integer>
<key>Minute</key><integer>0</integer>
</dict>
Loaded into ~/Library/LaunchAgents/, it runs at 7am whether the script has ever been touched again or not.
What Broke
Problem 1: MCP Gmail tools don't work headless
My first attempt used the Gmail MCP server for sending. Works perfectly in interactive Claude Code sessions โ the server is already authenticated. But in a headless launchd job, the MCP server isn't running. Claude can call the tool schema but the server isn't there to respond. Silent failure.
Fix: Switched to gws CLI, which hits the Gmail API directly via stored OAuth credentials. No server dependency. Works anywhere the binary is on PATH.
Problem 2: Claude repeated stories from prior days
Without deduplication, Claude would search for "Amazon news" and surface the same stories it had already sent โ articles that were published three days ago but were still "top results." The second morning, the team was reading yesterday's briefing again.
Fix: The history file. Claude is instructed to write the headline of every story it sends โ one per line, prefixed with the date โ to briefing-history.txt. The next run loads the last 70 entries (approximately one week) into the prompt as "do not repeat these." This produces genuinely new content each morning even in slow news cycles. Claude will acknowledge "it was a slow news day" rather than recycling.
The history file trims itself to 70 entries at the end of each run so it doesn't grow unbounded.
Problem 3: HTML email rendering inconsistencies
The first few emails had broken layouts in Gmail โ headings too large, paragraph spacing off, links not styled. Claude's default HTML output uses <style> blocks in the <head>, which Gmail strips.
Fix: Added a specific instruction to the prompt: "use inline CSS only โ no <style> blocks, no external stylesheets." Once that constraint was explicit, the HTML rendered correctly across Gmail, Outlook, and Apple Mail.
Problem 4: Stories without working URLs
Occasionally Claude would include a story with a plausible-looking but dead URL โ hallucinated source links, or a URL that worked during the search but 404'd by send time. The team would click through and get nothing.
Fix: Added to the prompt: "Every story MUST have a working source link. Only include stories from the last 24 hours with verifiable primary source URLs." This didn't eliminate the problem entirely, but it reduced the frequency significantly. The history log lets me spot patterns โ if a certain source appears repeatedly with dead links, I adjust.
The Output
The briefing that lands in the VS inbox at 7am looks like this:
- E-Commerce News header, 5 stories:
- Headline as a clickable link to the source
- 3-4 sentence "why it matters" written for operators managing $100K+/month brands โ not abstract, not general tech news commentary
- AI & Tech News header, 5 stories, same format
- Sign-off
The synthesis is the part Claude does better than a human skimming headlines. A headline like "Amazon updates Rufus backend model" gets turned into "Here's how this changes what shoppers see when they ask about your category, and what that means for your attribute completeness this week." That's the 3-4 sentences that nobody has time to write at 7am.
Cost and Time Math
API cost:
- Each run: approximately 12-15K input tokens (prompt + history + web search results) + 3-4K output tokens
- At current Sonnet 4.6 pricing: ~$0.15-0.20/day
- Monthly: $4.50-6.00
- Annually: ~$55-70
Time saved:
- Pre-automation: ~35 min/day across the leadership team
- Post-automation: 8-minute briefing read, 0 minutes of research
- Net savings: ~27 min/day per person who reads it
- Across 4 readers: ~108 min/day = 1.8 hours/day in recovered attention
- Monthly: ~36 hours
At $150/hour in senior operator time, that's $5,400/month in recovered capacity for $6 in API spend. The ratio is not subtle.
What an Operator Could Replicate
You don't need an agency to run this. The components are:
- Claude Code CLI โ install it, authenticate once.
- A launchd plist (Mac) or systemd service (Linux) โ schedule the script.
- gws CLI or equivalent โ any CLI email sender with stored credentials works.
- A prompt that gives Claude: the audience, the topic focus, the email format, the deduplication instruction, and the send mechanism.
The operational overhead after setup is zero. I haven't touched daily-briefing.sh in weeks. It runs, logs, sends, and trims its own history.
The only maintenance it requires is occasionally glancing at the log when a run fails โ usually a network timeout on the web search, which the next morning's run recovers from automatically.
The Pattern Behind the Pattern
What I actually built isn't a briefing tool. It's a pattern I've started applying to other repetitive intelligence tasks: give Claude a scoped set of tools, a specific audience and output format, a deduplication mechanism, and an automated trigger.
The fathom-to-Todoist sync I wrote about earlier follows the same pattern. The Premium A+ generator follows the same pattern. The recurring element: Claude handles the cognitive labor (finding, synthesizing, writing), the shell handles the orchestration (trigger, logging, state), and a simple CLI handles the output (send, publish, write).
The cases where this breaks down are the cases where the output requires subjective judgment that isn't expressible in a prompt โ visual creative decisions, client-specific strategic calls, anything where the right answer depends on institutional knowledge that isn't in the prompt. Those stay manual. Everything else is a candidate for automation.
The daily briefing was the easiest version of this pattern because the task is completely well-defined: find stories, explain them, send them. No judgment required that Claude can't make from the prompt context.
If you're running an Amazon brand or an agency and there's a recurring research task you do every week โ competitive monitoring, search term reporting synthesis, review pattern analysis โ the question is whether the task is well-defined enough to express in a prompt. Most of them are.