Over 31 days, a list of 26 brands from one home produced 38 text messages, 21 of them about a distinct product recall, for about $2 in Twilio fees. That is the whole result. The rest of this post is the 80-line script that did it and what the log looked like.
The recall tracker is a Python script that runs once a day, asks a news API for headlines with the word "recall" and any name from a text file, and sends each new headline to your phone through Twilio. No app, no account with a recall service, no six agency mailing lists. If you can run a Python file from a terminal, you can run this.
One honest note before the numbers. We wrote the script this month. Instead of waiting a month to publish, we replayed the same daily query over August 17 to September 16, 2026 with a --dry-run flag that prints texts instead of sending them. Every number below comes from that replay, and the CSV it wrote is next to this post.
Takeaways
- 26 names on the list, 47 API hits in 31 days, 38 texts after dedup and a name check.
- 21 distinct recalls, 10 repeat headlines about a recall already texted, 6 stories about a recall, 1 wrong match.
- 17 of the 26 names never came up. Walmart alone was 17 of 38 texts.
- 66 SMS segments cost $0.55 at Twilio plus carrier fees, plus $1.15 for the number.
- A per-brand cooldown does not help: one day of cooldown removes 13 texts and 3 real recalls.
Why not just subscribe to recalls.gov
Because the official lists send everything. Recalls.gov forwards notices from six US agencies with no way to say "only Toyota and Whirlpool". The U.S. Consumer Product Safety Commission (CPSC) email covers CPSC products only, so a car recall from the National Highway Traffic Safety Administration (NHTSA) or a frozen berry recall from the Food and Drug Administration (FDA) never arrives. Consumer Reports has a car recall tracker that texts you, but it is vehicles by VIN and nothing else.
| Source | Covers | Filter by brand | Texts you | API |
|---|---|---|---|---|
| cpsc.gov email / RSS | CPSC products only | No | No | Yes |
| recalls.gov email | Six agencies | No | No | No |
| Consumer Reports car tracker | NHTSA vehicles | By VIN | Yes | No |
| News API + this script | Anything a newsroom reported | Yes, your list | Yes, via Twilio | Yes |
Unlike the recalls.gov email, which sends every recall from six agencies, the script sends only headlines that name something on your list, which means 38 texts a month for 26 names instead of hundreds.
Step 1. Write down what you own
One name per line in my_stuff.txt. We used a plausible home: appliances, two cars, kid gear, the stores we shop at, a few food brands.
Samsung
LG
Whirlpool
Bosch
KitchenAid
Instant Pot
Dyson
Philips
Toyota
Honda
Ford
Subaru
Fisher-Price
Graco
IKEA
Peloton
Walmart
Costco
Trader Joe's
Kroger
Target
Great Value
Kirkland
Nestle
Tyson
Blue Buffalo
Store names matter more than you would guess. Food recalls are usually reported as "sold at Walmart, Target and Kroger", not by the packer's name.
Step 2. Find the filter that actually works
We used APITube, our News API Intelligence (disclosure at the bottom). Articles come back already tagged with entities, categories and events, which is why the obvious filters were the first thing we tried. They did not do what we hoped, so here is what each one returned, measured on the same day:
| Filter | What happened |
|---|---|
event.type=recall |
Accepted silently, returns the unfiltered feed. First hit was a story about tariffs. |
event.name=recall |
400 ER0228: "entity event name 'recall' not found". |
category.id=medtop:20000207 (IPTC "product recall") |
18 of the latest 197 English articles had "recall" in the headline. |
query=recall AND (Walmart OR Ford OR …) |
47 hits in 31 days, 45 of them with a listed name in the headline. |
So the working route is the boolean query parameter: a bare word searches headlines, and AND/OR combine them. One request covers the whole list.
curl -G "https://api.apitube.io/v1/news/everything" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode 'query=recall AND (Walmart OR Ford OR Toyota OR "Great Value")' \
--data-urlencode "language.code=en" \
--data-urlencode "published_at.start=2026-09-16" \
--data-urlencode "sort.by=published_at" \
--data-urlencode "sort.order=asc" \
--data-urlencode "per_page=200"
Each result is a large object. The script uses five fields of it:
{
"id": 3083205852,
"title": "Fuel Tank Issue Sparks Ford F-150 Recall",
"href": "https://lite987.com/ixp/39/p/ford-recall-new-york-state-drivers/",
"published_at": "2026-09-16T13:21:38.000Z",
"source": { "domain": "lite987.com" }
}
Names with a space, apostrophe or hyphen go in quotes ("Great Value", "Trader Joe's", "Fisher-Price"). The script does that for you.
Step 3. The script
Save this as recall_alerts.py next to my_stuff.txt. It needs requests and twilio (pip install requests twilio).
import csv, os, re, sys, pathlib, datetime as dt
import requests
API = "https://api.apitube.io/v1/news/everything"
HERE = pathlib.Path(__file__).parent
NAMES = [n.strip() for n in (HERE / "my_stuff.txt").read_text().splitlines() if n.strip()]
LOG = HERE / "alerts.csv"
DRY = "--dry-run" in sys.argv
def arg(flag, default=None):
return sys.argv[sys.argv.index(flag) + 1] if flag in sys.argv else default
def term(name):
return f'"{name}"' if re.search(r"[\s'\-]", name) else name
QUERY = "recall AND (" + " OR ".join(term(n) for n in NAMES) + ")"
def fetch(start, end=None):
params = {
"query": QUERY,
"language.code": "en",
"published_at.start": start,
"sort.by": "published_at",
"sort.order": "asc",
"per_page": 200,
"page": 1,
}
if end:
params["published_at.end"] = end
while True:
r = requests.get(API, headers={"X-API-Key": os.environ["APITUBE_API_KEY"]}, params=params, timeout=60)
r.raise_for_status()
data = r.json()
yield from data["results"]
if not data.get("has_next_pages"):
return
params["page"] += 1
def title_key(title):
return re.sub(r"[^a-z0-9]+", " ", title.lower()).strip()
def names_in(title):
return [n for n in NAMES if re.search(r"(?<![A-Za-z])" + re.escape(n) + r"(?![A-Za-z])", title)]
def send_sms(text):
if DRY:
print(text, "\n")
return "dry-run"
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
msg = client.messages.create(body=text, from_=os.environ["TWILIO_FROM"], to=os.environ["MY_PHONE"])
return msg.sid
rows = list(csv.DictReader(LOG.open())) if LOG.exists() else []
seen = {row["title_key"] for row in rows}
since = arg("--since") or (rows[-1]["published_at"] if rows else (dt.datetime.utcnow() - dt.timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ"))
until = arg("--until")
with LOG.open("a", newline="") as f:
w = csv.writer(f)
if not rows:
w.writerow(["published_at", "article_id", "names", "title_key", "title", "url", "source", "sms_chars", "sid"])
for a in fetch(since, until):
key = title_key(a["title"])
names = names_in(a["title"])
if key in seen or not names:
continue
text = f"RECALL ({', '.join(names)}): {a['title']} {a['href']}"
sid = send_sms(text)
w.writerow([a["published_at"], a["id"], "|".join(names), key, a["title"], a["href"], a["source"]["domain"], len(text), sid])
seen.add(key)
What each part does, in plain words:
-
QUERYbuilds therecall AND (…)string from your file. -
fetch()asks the API for everything since the last run, 200 at a time, and followshas_next_pages. -
title_key()turns a headline into lowercase letters and digits. Syndicated copies (the same F-150 headline ran on three radio station sites) collapse into one key, so you get one text, not three. -
names_in()checks the headline again for your names, case-sensitive and whole-word. The API matches loosely; this second check decides which names go into the text. -
send_sms()prints in--dry-runmode and calls Twilio otherwise. Four environment variables:TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN,TWILIO_FROM(your Twilio number) andMY_PHONE. A trial account can only text numbers you verified in the console, and it prefixes every message with a trial notice. -
alerts.csvis both the log and the memory. The lastpublished_atin it is where the next run starts.
Step 4. Run it, then let cron run it
First run, dry, from a date you pick:
export APITUBE_API_KEY=... # free tier at apitube.io
python3 recall_alerts.py --dry-run --since 2026-09-10
Once the printed texts look right, add the Twilio variables and a cron line. Once a day at 08:00 is enough. Recalls are not breaking news; the article is usually hours behind the official notice anyway.
0 8 * * * cd ~/recall-alerts && python3 recall_alerts.py
What 31 days looked like
The replay ran --since 2026-08-17 --until 2026-09-16T23:59:59Z. 47 API hits became 40 unique headlines, and 38 texts after the name check. We then read all 38 and labelled them by hand.
| Label | Texts | What it means |
|---|---|---|
| Distinct recall | 21 | First headline about a recall you had not been told about |
| Repeat coverage | 10 | Another outlet, another headline, same recall |
| About a recall | 6 | A stock story, a sales story, a lawyer ad, a listicle, commentary, a Honda "check your recall status" notice |
| Wrong | 1 | Doug Ford, the Ontario premier, being asked to recall the legislature |
| Name | Texts |
|---|---|
| Walmart | 17 |
| Ford | 11 |
| Toyota | 6 |
| Target | 5 |
| Kroger | 3 |
| Costco | 2 |
| Great Value | 2 |
| Honda | 1 |
| Subaru | 1 |
(A text can carry more than one name, so the column adds up past 38.) The other 17 names, from Samsung to Blue Buffalo, produced nothing in 31 days. The Walmart frozen-berry E. coli recall alone produced 8 texts over two weeks as it expanded state by state. The busiest day was September 4 with 5 texts; 24 of the 31 days had at least one.
The name check dropped 2 of the 47 hits. One drop was right: a Tesla door-handle recall matched because the headline contained the verb "target". One was a loss: a Goal Zero power-station recall posted on a Costco fan site, where "Costco" appeared only in the body. Title-only matching has a price, and that was it for the month.
What it costs
A recall tracker for 26 names costs about $2 a month: 66 SMS segments in 31 days at Twilio's US long-code rate of $0.0083 per segment ($0.55), carrier pass-through fees of $0.0035–0.005 per segment ($0.23–0.33), and $1.15 a month for the number. $1.93 to $2.03 for the month. 28 texts needed two segments because the article URL pushes them past 160 characters; 10 fit in one.
One detail that matters for the bill: the text starts with RECALL (Walmart): and not Recall — Walmart. An em dash is not in the GSM-7 character set, and a single one switches the whole message to 70-character segments.
Should you add a cooldown?
The obvious fix for 8 berry texts is "don't text about the same name twice in N days". We replayed the log with that rule:
| Cooldown per name | Texts | Distinct recalls caught |
|---|---|---|
| None | 38 | 21 of 21 |
| 1 day | 25 | 18 of 21 |
| 3 days | 20 | 15 of 21 |
| 7 days | 14 | 10 of 21 |
A one-day cooldown removes 13 texts, and 3 of those were distinct recalls: a Ford Bronco airbag recall, a Toyota GR Supra recall in China and a UK roundup naming Ford and Toyota. Ford had five separate recalls in the month, and they land days apart. A per-name cooldown is the wrong lever for recall alerts, because it throws away real recalls faster than it removes repeats. Our recommendation: no cooldown. If 38 texts is too many, shorten the list. Taking Walmart off it leaves 28 texts; taking Walmart and Ford off leaves 18.
FAQ
How do I get notified about product recalls?
The two ways to get notified about product recalls are the recalls.gov email, which sends every notice from six US agencies, and a script like the one above, which texts you only when a headline names something you own. The official email is complete but unfiltered; the script is filtered but only as good as the newsroom coverage.
Is there a product recall API?
There is no single product recall API for all products. The CPSC Recall API covers consumer products under its jurisdiction, not vehicles, food or drugs. For everything else, a news API with a headline search covers whatever gets reported: query=recall AND (Brand OR Brand) returned 47 headlines in 31 days for 26 names.
Is there an app for product recalls?
We did not find an app that watches product recalls for a personal list of brands across all agencies. Consumer Reports covers vehicles and several retailers cover their own sales. This script is the app: a text file, a cron line and about $2 a month.
Does Amazon notify you of recalls?
Amazon notifies you of recalls only for items bought on Amazon, by email and a notice in Your Orders. It does not know about the Instant Pot you bought elsewhere, which is what the list in my_stuff.txt is for.
Limits
- Title-only matching misses recalls whose headline names the product and not the brand or store. The Goal Zero power-station recall was the one we lost this month: "Costco" was in the body, not the headline.
- Title dedup merges identical headlines, not different headlines about one recall. Expect a few texts per big recall.
- The news is hours behind the official notice. The text links to the article; check the lot number and dates there or on the agency site before you throw anything out.
- 17 of 26 names were silent for a month. That is a normal month, not a broken script.
The script, my_stuff.txt and the 38-row data.csv from the replay are next to this post. Change the list and run it.
Resources
- recalls.gov — the six-agency hub and its email subscription
- CPSC Recall API — official API for CPSC-jurisdiction recalls
- Twilio Messaging quickstart for Python — client setup and trial-account rules
-
APITube docs: /v1/news/everything — the
queryparameter and its 31-day window
Disclosure: APITube is our product, a News API Intelligence, and the API used above. It has a free tier at apitube.io.


Top comments (0)