I Automated My Weekly Grocery Shopping with Two Agent-Native Tools

How I used AgentMail + AgentBrowser to build a fully automated weekly grocery workflow — from Telegram voice messages to Barbora cart.

MS
Manpreet Singh Gulati
8 min read

I live in Estonia. My family orders groceries from Barbora.ee — it’s the local equivalent of Instacart, and it’s how we eat every week. The process was simple: I’d open the app on Friday, remember what we needed, add items, and check out. Simple, but I kept forgetting things. Or I’d be in a meeting when the delivery slot reminder popped up.

So I automated the entire thing — and I mean entire. Items arrive via Telegram voice messages, get collected in a running list, previewed by email on Thursday, and added to cart on Friday. The human only has to check out and pay. Here’s exactly how it works.

The Architecture#

The system ties together four pieces:

ComponentRoleWhen
Hermes AgentOrchestration — collects items, runs cron jobs, resolves product IDsAlways on
Barbora APIProduct search — open endpoint, no auth neededResolution phase
AgentMailThursday preview emails via agentmail.ioThu 17:00 UTC
AgentBrowserFriday cart additions — headless Chrome with saved auth sessionFri 09:00 UTC

The flow:

  1. All week: I send items via Telegram text or voice message → Hermes adds them to a JSON list
  2. Thursday 17:00 UTC: Cron job reads the list, emails a preview via AgentMail
  3. Friday 09:00 UTC: Cron job resolves each item to a real Barbora product, opens AgentBrowser, adds everything to cart, applies a coupon

The human’s job: open Barbora on Friday evening, pick a delivery slot, pay.

What I Learned About Barbora’s API#

Before touching any browser automation, I checked if Barbora had an API I could talk to directly. Turns out their SPA exposes a REST API that’s perfectly usable:

curl -s "https://barbora.ee/api/eshop/v1/search?query=2L+piima" \
  -H "Accept: application/json" \
  -H "User-Agent: Mozilla/5.0" \
  -H "Referer: https://barbora.ee/"

This returns clean JSON with product IDs, prices, categories, and stock status. No auth needed. This was the first big win — product search is the most frequent operation and it’s completely stateless.

I built a small Python CLI around it:

{
    "item": "2L piima",
    "status": "resolved",
    "product_id": "000000000001394543",
    "product_name": "Piim WELL DONE 2,5% UHT, 1L",
    "quantity": 2,
    "price_per_unit": 0.99
}

Each item gets stored in a JSON file with a status lifecycle: pendingresolved (product matched) → added (to cart).

The Thursday Preview: AgentMail#

The Friday cron job adds items to the cart automatically. But I wanted a human-in-the-loop check — a chance to see what’s going into the cart before it actually happens. That’s where AgentMail came in.

AgentMail gives each AI agent its own email inbox. I set one up at alguslabs-assistant@agentmail.to and configured a Thursday cron job to:

  1. Read the current grocery list
  2. Compose a formatted email
  3. Send it to my personal Gmail

The email arrives looking like this:

🛒 Grocery Preview — Tomorrow's Cart
 
1. 2L piima → Piim WELL DONE 2,5% UHT, 1L × 2 (€0.99 ea) — resolved
2. baby spinach → Beebispinat WELL DONE 200g (€1.99) — resolved
3. keenwa → Valge kinoa JUST NATURE 500g (€3.85) — resolved
...
Total: €23.87

If anything looks wrong (wrong product, price seems off), I have a day to fix it before the Friday run.

The AgentMail MCP tools gave me a send_message function that felt like any other tool call. The setup was straightforward — create an inbox, configure credentials, and the agent can send and receive email as part of its workflow.

The Friday Execution: AgentBrowser#

Cart additions are the one part that does need authentication. Barbora’s add-to-cart endpoint requires a session cookie (.BRBAUTH) that you only get after logging in. You can’t just curl it.

Enter AgentBrowser by Vercel Labs — a headless browser controlled entirely through an agent-friendly CLI. No Playwright scripts, no Puppeteer, no Selenium. Just commands like open, click, fill, eval, snapshot.

The trick was not to navigate and click through the UI for each item. That would be slow and fragile — one CSS class change and the whole thing breaks. Instead, I:

  1. Open Barbora in AgentBrowser with the saved auth state
  2. Use eval to run JavaScript fetch calls in the page context, where the auth cookie is automatically sent
# Launch Chrome and connect AgentBrowser
/tmp/chromium/chrome-linux/chrome --no-sandbox --headless --disable-gpu \
  --remote-debugging-port=9222 https://barbora.ee &
 
CDP_WS=$(curl -s http://localhost:9222/json/version | \
  python3 -c "import sys,json; print(json.load(sys.stdin)['webSocketDebuggerUrl'])")
 
agent-browser connect "$CDP_WS"
 
# Load saved auth state and navigate to Barbora
agent-browser state load ~/.hermes/data/barbora-auth.json
agent-browser open https://barbora.ee/
 
# Add item to cart via the internal API
agent-browser eval "fetch('/api/eshop/v1/cart/item', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    product_id: '000000000001394543',
    quantity: 1
  })
}).then(r => r.json()).then(d => JSON.stringify(d))"

This is the key pattern: use the browser only for the auth context, then talk to the internal API directly. The browser handles cookies, CORS, and authenticated sessions. We handle the API calls. It’s faster and more reliable than clicking through pages.

After adding all items, a second eval call applies the coupon code and fetches the cart total:

agent-browser eval "fetch('/api/eshop/v1/cart/getsinglebasket')
  .then(r => r.json())
  .then(d => JSON.stringify({
    count: d.products?.length, 
    total: d.total
  }))"

I also take a screenshot so I can visually confirm the cart looks right:

agent-browser screenshot ~/.hermes/data/barbora-cart-screenshot.png

Scheduling: Hermes Cron#

Both the Thursday email and Friday cart run as Hermes cron jobs. The Hermes gateway process has a built-in scheduler that fires jobs on schedule.

# ~/.hermes/crons.yaml
- name: barbora-thursday-preview
  schedule: "0 17 * * 4"
  run: grocery-preview
 
- name: barbora-friday-grocery
  schedule: "0 9 * * 5"
  run: grocery-cart-add

But here’s a detail that bit me: missed-run fast-forward. If the gateway is down when a cron is due, the scheduler doesn’t replay missed ticks. It silently fast-forwards to the next scheduled occurrence. This is smart — it prevents a flood of stale runs when the gateway comes back up. But it also means if your server was off for Friday morning, you don’t get your groceries that week unless you trigger the job manually:

hermes cron run barbora-friday-grocery
hermes cron tick

The Gotchas#

A few things that surprised me along the way:

1. AgentBrowser’s --args flag didn’t work#

Version 0.26.0 has a bug where --args "--no-sandbox" is silently ignored. The fix was to start Chrome separately with CDP and connect AgentBrowser to it — the “CDP connect” pattern shown above.

2. Auth sessions expire#

Barbora’s login cookie lasts about a week. If the session expires between the Thursday preview and Friday execution, the cart fails silently. I now check for 401 responses on the cart API and trigger a re-login flow if needed.

3. AgentMail’s MCP tools can 403#

The MCP bindings returned HTTP 403 even when the CLI and SDK worked fine. The workaround was to switch to the Node.js SDK directly when MCP fails — a one-line fallback in the cron job script.

4. Item matching is harder than it looks#

A Telegram voice message saying “add keenwa” needs to become “Valge kinoa JUST NATURE 500g (€3.85)“. The search has to handle misspellings, Estonian vs English names, and ambiguous terms. I ended up keeping the agent in the loop — it searches, picks the best match, and flags anything uncertain for manual review.

5. Voice messages need transcription#

When I’m walking home and remember we need milk, I just say “add 2 liters of milk” into Telegram. Hermes transcribes the voice message, parses it as a grocery item, and adds it to the list. This is the most convenient part of the whole system and the reason I actually use it consistently.

What I’d Do Differently#

If I were building this again, I’d consider:

  • A proper database instead of JSON files. The JSON approach works fine for 20 items a week, but concurrent writes from Telegram handlers and cron jobs can race.
  • Dedup by product, not by text. Right now “earbuds” added twice creates two separate cart items. Deduplicating by resolved product ID would be cleaner.
  • Delivery slot reservation. The one thing I still do manually is pick a 1-hour delivery window on Friday evening. Barbora releases slots at specific times, and reserving one automatically is the next frontier.

The Takeaway#

The most useful pattern I hit on was using the browser only for authentication context, then working through the site’s own API from within that context. This hybrid approach gives you the reliability of a proper API with the authorization flow of a browser session. It works for any SPA where the internal API isn’t aggressively locked down.

AgentMail and AgentBrowser filled two very different gaps: email as a human-readable checkpoint, and browser auth as a bridge to an otherwise closed API. Neither required more than a few lines of configuration. Together they turned a Friday chore into a fully autonomous system.

The only manual step left is tapping “Pay” on my phone. I’ll automate that too — once I’m sure I won’t accidentally order 50kg of potatoes.

Share