claude-code·8 min read·
Build a Claude Code morning brief agent (UK indie hacker, 2026)
A full Claude Code morning brief agent built in a single session. Pulls Companies House new directorships against your watchlist, GSC trailing 7d, and Met Office DataPoint, drafts the brief in Sonnet, sends via Resend at 6:55 every weekday. Deployable code, GBP cost line, smoke check, full pattern end-to-end.

A real story. A UK indie hacker wakes up, checks Companies House for any directorship changes that hit overnight on their watchlist of competitors, glances at GSC to see if anything spiked yesterday, looks at the weather, sips coffee, decides what to ship that day. The whole ritual takes 12 minutes and uses 4 browser tabs. Most days nothing material happens and the 12 minutes is pure overhead.
The fix is a Claude Code agent that does the same scan, summarises into 6 lines, and emails the brief at 06:55 before they sit down. Twelve minutes of context-gathering becomes 90 seconds of reading. When the brief flags something material, the day's first action is already clear. When it doesn't, the morning is properly free.
This post is the full pattern end to end. Three UK data sources, one Claude call to write the brief, one Resend call to ship it, one systemd timer or cron to schedule it. Deployable code, the cost line in GBP, the smoke check that confirms it's working. All of the prior Claude Code in production cluster posts (scheduling matrix, Hetzner VPS, observability collect and alert, WDK durable workflows, Raspberry Pi, Fly.io scale-to-zero) come together here into a single shippable agent.
The architecture in plain English
Five components, glued together by a single shell script that fires once per weekday morning.
- Companies House client. Fetch new filings for each company on a watchlist (3-10 competitor company numbers).
- GSC client. Pull the last 7 days of impressions and clicks for your own site.
- Met Office client. Pull the day's UK forecast for your location.
- Claude Code synthesiser. Combine the three structured inputs into a 6-line markdown brief.
- Resend dispatcher. Send the brief as an email to your inbox at 06:55.
The script runs as a single sequential pipeline. Each step is ~5-10 lines of code. The whole agent is under 200 lines including config. It costs roughly GBP 0.04 per run, fires once per weekday morning, and lands a fresh brief in your inbox before you sit down.
Step 1: Companies House watchlist
The Companies House Public Data API is free, has a 600-request per 5-minute window per key, and gives you new filings against any company number. Register at developer.company-information.service.gov.uk, generate an API key, store it in the env.
A small Python module to pull recent filings for a watchlist:
# companies_house.py
import os, requests
from datetime import datetime, timedelta
CH_KEY = os.environ["COMPANIES_HOUSE_KEY"]
WATCHLIST = ["12345678", "23456789", "34567890"] # company numbers
SINCE = datetime.utcnow() - timedelta(hours=24)
def fetch_recent_filings():
out = []
for company_no in WATCHLIST:
r = requests.get(
f"https://api.company-information.service.gov.uk/company/{company_no}/filing-history",
auth=(CH_KEY, ""),
params={"items_per_page": 10},
timeout=15,
)
if r.status_code != 200:
continue
for item in r.json().get("items", []):
filed = datetime.strptime(item["date"], "%Y-%m-%d")
if filed >= SINCE - timedelta(days=2):
out.append({
"company": company_no,
"type": item.get("type"),
"description": item.get("description"),
"date": item["date"],
})
return out
That's the entire Companies House half. The output is a list of structured filings from the last ~48 hours, ready for Claude to summarise.
Step 2: GSC trailing 7-day pull
The Google Search Console API is also free. Service account auth, same key file the rest of your Claude Code stack uses, no per-call cost. The 7-day window is small enough that one query returns everything.
# gsc.py
from google.oauth2 import service_account
from googleapiclient.discovery import build
from datetime import date, timedelta
KEY = "/etc/secrets/google-service-account-key.json"
SITE = "sc-domain:yourdomain.co.uk"
def fetch_gsc_summary():
creds = service_account.Credentials.from_service_account_file(
KEY, scopes=["https://www.googleapis.com/auth/webmasters.readonly"]
)
svc = build("searchconsole", "v1", credentials=creds)
end = date.today() - timedelta(days=2) # GSC has 2-day lag
start = end - timedelta(days=6)
r = svc.searchanalytics().query(
siteUrl=SITE,
body={"startDate": str(start), "endDate": str(end),
"dimensions": ["page"], "rowLimit": 5},
).execute()
return {"window": [str(start), str(end)], "top_pages": r.get("rows", [])}
Pulled in 2 seconds, no cost.
Step 3: Met Office DataPoint
Met Office DataPoint gives you UK weather data on a free tier (5,000 calls/day). Register at datahub.metoffice.gov.uk, get an API key, pick the nearest weather station to your location.
# weather.py
import os, requests
MET_KEY = os.environ["METOFFICE_KEY"]
LOCATION_ID = "352409" # London - replace with your nearest station
def fetch_weather():
r = requests.get(
f"https://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/json/{LOCATION_ID}",
params={"res": "daily", "key": MET_KEY},
timeout=15,
)
if r.status_code != 200:
return {"error": r.status_code}
period = r.json()["SiteRep"]["DV"]["Location"]["Period"][0]
today_day = next((p for p in period["Rep"] if p["$"] == "Day"), None)
return {
"summary": today_day.get("W", "unknown"),
"max_temp": today_day.get("Dm"),
"rain_pct": today_day.get("PPd"),
}
Returns a small structured dict that Claude can read in one go.
Step 4: Claude synthesiser
The key step. Pass the three structured inputs to Claude as JSON and ask for a 6-line markdown brief. Headless claude -p is the simplest invocation - one shell-out, ~2k tokens, GBP 0.04 of Sonnet cost.
# synthesise.py
import json, subprocess
from companies_house import fetch_recent_filings
from gsc import fetch_gsc_summary
from weather import fetch_weather
def build_brief():
inputs = {
"filings": fetch_recent_filings(),
"gsc": fetch_gsc_summary(),
"weather": fetch_weather(),
"date": __import__("datetime").date.today().isoformat(),
}
prompt = f"""You are writing a 6-line morning brief for a UK indie hacker.
Today is {inputs['date']}.
Structured inputs (JSON):
{json.dumps(inputs, indent=2, default=str)}
Output a markdown brief with these sections, terse and useful:
- TL;DR (1 line)
- Companies House watchlist (1-2 lines, omit if no material filings)
- GSC trailing 7d (1 line, top page or notable change)
- UK weather today (1 line, action-implied)
- Suggested first action (1 line)
EN-UK spelling. Plain text. No filler."""
result = subprocess.run(
["claude", "-p", prompt],
capture_output=True, text=True, timeout=120,
)
return result.stdout.strip()
One shell-out, one 2k-token call to Sonnet, one ~6-line markdown brief. The prompt is constrained enough that the output is reliably terse.
Step 5: Resend dispatcher
Resend handles the email. Free tier covers 100 emails/day, deliverability for UK indie hackers is excellent after one round of SPF/DKIM verification. The API call is one POST.
# send.py
import os, requests
RESEND_KEY = os.environ["RESEND_API_KEY"]
FROM = "brief@yourdomain.co.uk"
TO = "you@yourdomain.co.uk"
def send_brief(body):
r = requests.post(
"https://api.resend.com/emails",
headers={"Authorization": f"Bearer {RESEND_KEY}",
"Content-Type": "application/json"},
json={
"from": FROM,
"to": [TO],
"subject": f"Morning brief - {__import__('datetime').date.today().isoformat()}",
"text": body,
},
timeout=15,
)
return r.status_code, r.json()
Status 200 means the brief is on its way.
The glue script
A single Python entrypoint that runs the pipeline end-to-end:
# brief.py
from synthesise import build_brief
from send import send_brief
def main():
body = build_brief()
status, response = send_brief(body)
print(f"sent: {status}", response)
if __name__ == "__main__":
main()
Twelve lines including imports. The whole agent is now 5 small files plus this glue, totalling under 200 lines.
Step 6: schedule it for 06:55 weekdays
Three good options. Pick the one that fits your host.
Option A. systemd timer on a Hetzner VPS or Raspberry Pi.
# /etc/systemd/system/brief.service
[Service]
Type=oneshot
EnvironmentFile=/etc/secrets/brief.env
WorkingDirectory=/opt/brief
ExecStart=/usr/bin/python3 brief.py
# /etc/systemd/system/brief.timer
[Timer]
OnCalendar=Mon..Fri 06:55:00 Europe/London
Persistent=true
[Install]
WantedBy=timers.target
Enable with systemctl enable --now brief.timer. Confirm with systemctl list-timers brief.timer.
Option B. GitHub Actions cron for Fly.io scale-to-zero.
# .github/workflows/brief.yml
name: Morning brief
on:
schedule:
- cron: "55 5 * * 1-5" # UTC, adjust for BST
jobs:
fire:
runs-on: ubuntu-latest
steps:
- name: Trigger Fly machine
run: |
curl -X POST "https://${{ secrets.FLY_APP }}.fly.dev/run-brief" \
-H "Authorization: Bearer ${{ secrets.BRIEF_KEY }}"
The Fly machine wakes up, runs brief.py, sends the email, scales back to zero. Free.
Option C. plain cron on any Linux box.
55 6 * * 1-5 cd /opt/brief && /usr/bin/python3 brief.py >> /var/log/brief.log 2>&1
Crude but works.
The test is the same regardless of option: set the schedule for two minutes from now, watch the log or your inbox, confirm the brief arrives. If it does, switch the time back to 06:55 and forget about it.
The cost line, honest
| Component | Monthly cost (GBP) |
|---|---|
| Claude Sonnet (~2k tokens/run * 22 weekday runs) | 0.80 |
| Companies House API | 0.00 (free tier) |
| Met Office DataPoint | 0.00 (free tier) |
| Google Search Console | 0.00 (free) |
| Resend (22 emails/month, free tier covers 3,000/mo) | 0.00 |
| Host: Fly.io scale-to-zero | 0.20 |
| Host: Hetzner CX11 (if already running for other agents) | 0.00 marginal |
| Total marginal cost | 1.00/mo |
Plus the GBP 16 Pro subscription if you do not already have it. For an indie hacker who already runs Claude Code daily, the brief adds GBP 1/month to the bill. That covers 12 minutes of saved morning time five days a week, which is roughly 4 hours of unprompted thinking time recovered every month.
Smoke check and what to watch in week 1
Three things to monitor for the first week.
Delivery. Every brief should land in your inbox within 30 seconds of 06:55. If it does not, check the timer / cron log first, the Resend send-log second, the Claude call third. Most failures are timer scheduling typos.
Tone. Claude's first-week output is usually slightly verbose for "6 lines". Tighten the prompt with explicit max-line-counts ("exactly one line per section") if needed. The output stabilises after 3-4 days of iteration.
Watchlist accuracy. Companies House filings include lots of harmless ones (annual confirmation statements, accounts filings). After a week, add a filter in companies_house.py to ignore the noisy filing types and keep only the material ones (director appointments, charges, dissolutions, capital changes).
After a week of clean delivery, the brief is part of the morning. The audit doc earlier today walks through what to do if you want to push spend or cost-attribute the brief specifically; the Pool A vs Pool B audit covers how Pool B usage gets reported in the console from 15 June.
Extending the agent
Three sensible next steps once the baseline brief is shipping cleanly.
Add ONS or HMRC tax-deadline alerts. Free public data, structured similarly to Companies House. The synthesiser prompt expands by one section.
Add a second brief at 17:00 with the day's GA4 / Plausible numbers and todo-list nudge. Same architecture, different inputs, same Resend.
Wire the brief into Slack instead of email. Resend supports webhooks; alternatively swap send.py for a Slack chat.postMessage call. Two-line change.
The wider point. A morning brief used to be a Claude Cowork-only pattern - point-and-click, easy, but consumer-shaped. With Claude Code (headless, version-controlled, host-agnostic) and the UK data sources that already publish free APIs, the same brief becomes a 200-line indie-hacker-owned piece of code that costs GBP 1/month to run. That's the difference between "I use an AI thing" and "I built an AI thing that runs my morning". For a UK indie hacker who already ships with Claude Code, this is the natural capstone build of the "Claude Code in production" cluster - turning all the prior posts' theory into a single shippable agent.
New here? IdeaStack publishes one deeply researched UK business opportunity every Thursday - real keyword data, competitor analysis, builder prompts. See the latest free report.
Frequently asked
Why a Claude Code morning brief and not Claude Cowork?
Claude Cowork is the consumer scheduled-tasks product - point and click, easy, but you do not own the code, the data pipeline, or the deploy path. The morning brief in this post is Claude Code (headless `claude -p` plus the Agent SDK) so you own every line. That matters when you want to wire in UK-specific data sources (Companies House, Met Office DataPoint, GSC for your own site), version the prompts in git, run it on a Hetzner box or Fly machine you pay for in GBP, and tune the output without console-clicking. Cowork is fine for consumer use; for a UK indie hacker who builds the rest of their stack, Claude Code is the right tool.
Which UK data sources does the brief pull and what do they cost?
Three core UK sources, all free at indie scale. Companies House Public Data API (free, 600 requests per 5-minute window, registration at developer.company-information.service.gov.uk) for new directorships and filings against a watchlist. Met Office DataPoint (free tier, 5,000 calls/day, registration at datahub.metoffice.gov.uk) for the day's UK weather summary. Google Search Console (free, service account auth) for your site's trailing 7-day impressions and clicks. The brief also calls Claude (Sonnet, ~2k tokens, GBP 0.04 per run) to write the briefing prose from the structured inputs. Total per-run cost: GBP 0.04. Monthly cost for one brief per weekday: GBP 0.80 plus the GBP 16 Pro subscription if you do not already have it.
Where should I run this - Hetzner, Fly.io, or a Raspberry Pi?
All three work. The picker depends on duty cycle and your existing stack. A morning brief is single-shot scheduled - one run, 30 seconds, once per weekday. That makes it [Fly.io scale-to-zero](https://www.ideastack.co/blog/claude-code-fly-io-global-edge-worker-uk-indie-hacker-2026) shaped (about GBP 0.20/month machine time) or [Raspberry Pi at home](https://www.ideastack.co/blog/claude-code-raspberry-pi-home-server-uk-indie-hacker-2026) shaped (GBP 0/month after hardware). If you already have a [Hetzner VPS](https://www.ideastack.co/blog/claude-code-hetzner-vps-uk-indie-hacker-2026) running other agents, adding the morning brief is free. The decision is mostly aesthetic - the agent code is identical.
How do I schedule it for 6:55 weekday mornings?
Three good options depending on host. On a Linux VPS (Hetzner or Fly machine with min-machines-running), a [systemd timer](https://www.ideastack.co/blog/claude-code-linux-cron-routines-systemd-uk-indie-hacker-2026) with `OnCalendar=Mon..Fri 06:55:00 Europe/London` and the timezone honoured by the unit. On a Raspberry Pi, the same systemd timer or a plain cron entry. On scale-to-zero Fly, a GitHub Actions cron at `55 5 * * 1-5` (UTC during BST) that pokes the Fly machine endpoint, or Fly's own machine schedule preview. Test the trigger by setting it for two minutes from now and watching journalctl - if the brief arrives, the schedule works.
How do I get the email actually delivered without ending up in spam?
Use Resend. Resend gives 100 emails/day on the free tier and excellent UK deliverability with one DNS verification. Three setup steps: sign up at resend.com, add your domain and verify the SPF/DKIM records they generate, get an API key. The brief email goes via Resend's REST API with `from: brief@yourdomain.co.uk` (or whatever subdomain you verified), `to: you@yourdomain.co.uk`, plain-text or markdown body. For the first week of running the brief, send to a test address and review for tone, length, and accuracy before pointing it at your real inbox. After a week of clean sends Resend's reputation is fine for daily delivery.




