DEV Community

Rayne Robinson
Rayne Robinson

Posted on

I Replaced Twilio With a Docker Container. My Push Notifications Cost $0 Now.

My phone buzzed. "Receipt Scanned — Fry's — $42.15 (7 items)."

I was in the other room. My laptop was on the desk, lid open, running an expense tracker I built. My wife had pointed her phone at a grocery receipt. The AI read it, categorized every item, saved it to the database, and pushed a notification to both our phones — all before she put the receipt down.

No cloud service made that happen. No monthly subscription. No per-message fee.

This is a story about how I replaced paid push notification services with a single Docker container and wired it into an AI pipeline that talks to my phone for free.

The Problem

I build local AI tools. They run on my hardware, process data on my GPU, and store everything locally. But they had one gap: they were silent.

The expense tracker would scan a receipt and save it. The market scanner would finish a research pipeline. The knowledge base would ingest a YouTube transcript. All of it happened, and none of it told me.

I'd check in manually. Open the dashboard. Refresh. See what happened an hour ago. That's not infrastructure — that's babysitting.

I needed push notifications. Real ones. The kind that buzz your phone and show a title, a message, and an action button.

The Obvious Path (And Its Price Tag)

The industry answer is Twilio. Or Firebase Cloud Messaging. Or AWS SNS. Or OneSignal.

Service Cost Model Per Notification Monthly Minimum Your Data
Twilio SMS Per message ~$0.0079 $0 (pay-as-you-go) Routes through Twilio
Firebase/FCM Free tier + scaling $0 (with limits) $0 Routes through Google
AWS SNS Per million $0.50/million $0 Routes through AWS
OneSignal Freemium $0 (with limits) $0-$9+ Routes through OneSignal
Pushover One-time $0 after $5 $5 one-time Routes through Pushover

None of these are expensive in absolute terms. That's not the point.

The point is: every one of them routes your notification content through someone else's infrastructure. When my expense tracker sends "Receipt Scanned — Fry's — $42.15," that's spending data. Where I shop. What I spend. How often. Timestamped.

For a personal finance tool, that's the wrong tradeoff. Same principle from Part 4 — your receipt data shouldn't leave your machine. Neither should your notification content.

The Fix: ntfy

ntfy is an open-source push notification server. One Docker container. HTTP API. No account required for self-hosted. No API keys. No per-message billing.

You POST a message to a URL. Your phone receives it. That's the entire product.

# docker-compose.server.yml (excerpt)
ntfy:
  image: binwiederhier/ntfy
  command: serve
  ports:
    - "8090:80"
  volumes:
    - ntfy-cache:/var/cache/ntfy
  environment:
    - NTFY_AUTH_DEFAULT_ACCESS=deny-all
Enter fullscreen mode Exit fullscreen mode

Auth is deny-all by default. Token-based access. No anonymous publishing, no anonymous subscribing. The notification channel is as locked down as the data it carries.

Wiring It Into the AI Pipeline

I didn't want every tool to reimplement notification logic. So I built a shared module — one file, ~170 lines — that any tool in the stack imports:

from ntfy_helper import notify, receipt_scanned, pipeline_alert
Enter fullscreen mode Exit fullscreen mode

Three layers:

Layer 1 — Generic notifications. Any tool, any message.

notify("Pipeline complete: 37 opportunities scored")
Enter fullscreen mode Exit fullscreen mode

Layer 2 — Rich notifications. Title, priority, emoji tags, action buttons.

notify_rich(
    "Receipt Scanned",
    "Fry's — $42.15 (7 items)",
    priority=3,
    tags=["receipt", "white_check_mark"],
    actions=[("view", "Review", "http://[server-ip]:8006/expenses")]
)
Enter fullscreen mode Exit fullscreen mode

Layer 3 — Pre-built patterns. Domain-specific helpers that tools call with just data.

# Expense Tracker calls this after OCR completes
receipt_scanned("Fry's", 42.15, 7, transaction_id=1234)

# Any service calls this for pipeline status
pipeline_alert("ams", "Stage 5 complete: 37 opportunities scored")
Enter fullscreen mode Exit fullscreen mode

The action buttons are real. When a receipt notification hits my phone, there's a "Review" button that opens the expense tracker dashboard directly. One tap from notification to data.

The Mesh

Push notifications only matter if they reach you wherever you are. My wife is on the couch. I'm at my desk. She's on WiFi. I'm on ethernet. Both phones are on mobile data half the time.

The server runs on a machine in my house. Without a VPN, those notifications only work on the local network.

The enterprise answer is something like Twingate. Zero-trust network access, $5 to $10 per user per month. For a homelab with four devices and two people, that's $120 to $240 per year to connect your own machines to each other.

Tailscale's free tier covers 3 users and 100 devices. My entire mesh — server, laptop, two phones — costs $0/year.

Twingate (Teams) Twingate (Business) Tailscale (Personal)
Per user/month $5 $10 $0
Annual (2 users) $120 $240 $0
Device limit Varies Varies 100

Every device gets a stable private IP that works regardless of network. Home WiFi, mobile data, coffee shop. The phone subscribes to the server's mesh IP on port 8090 and notifications arrive everywhere.

Device Network Role
Server (dual 3090) Ethernet Sends
My phone Mobile/WiFi Receives
Wife's phone Mobile/WiFi Receives
Laptop Ethernet Sends + Receives

Four devices, one mesh, zero port forwarding, zero cloud relay.

The Fallback That Didn't Survive

I started with Twilio. Built the integration. It worked. SMS notifications to both phones.

Then I did the math on scale. If the expense tracker, market scanner, knowledge base, and every future tool each push 5-10 notifications per day across 2 phones, that's 20-40 SMS messages daily. At $0.0079 each:

  • Daily: $0.16-0.32
  • Monthly: $4.80-9.60
  • Yearly: $58-115

For push notifications from tools running on hardware I already own. From software I already wrote. The only cost is a third party's messaging pipe.

ntfy replaces all of that with a 15MB Docker container and zero ongoing cost.

The Twilio code is still in the repo. I keep it as a reference. It's never called.

What Actually Fires

Here's what happens when you scan a receipt with the expense tracker:

  1. Phone camera captures the receipt image
  2. Image hits the local server (RTX 3090, qwen3-vl:8b vision model)
  3. Vision model extracts store, items, prices, total
  4. Python parses and validates the OCR output
  5. Items get categorized by a text model (qwen3:8b)
  6. Everything saves to the database
  7. receipt_scanned("Fry's", 42.15, 7) fires
  8. ntfy pushes to both phones over Tailscale
  9. Both phones buzz with store name, total, item count, and a "Review" button

Steps 2-9 take about 8 seconds. The notification arrives before you put the receipt back in the bag.

The failure path matters too. If OCR can't read the receipt — bad lighting, crumpled paper, faded ink — the system pushes a different notification: "Receipt OCR Failed. Tap to enter manually." The action button opens the manual entry form. You're never left wondering if the scan worked.

The Numbers

Twilio SMS ntfy (Self-Hosted)
Cost per notification ~$0.008 $0.00
Monthly (40 notifs/day × 2 phones) ~$9.60 $0.00
Yearly ~$115 $0.00
Setup Account + API key + phone verification docker compose up
Data routing Through Twilio Device to device
Works offline (LAN) No Yes
Action buttons No (SMS) Yes
Rich formatting No (SMS) Yes (title, priority, emoji, markdown)
Scales to N tools Cost × N Same $0

The cost comparison gets more interesting as tools multiply. Every new service I build that needs notifications is one from ntfy_helper import notify away from having them. No new API keys. No new billing. No new vendor.

What I Learned

Push notifications are infrastructure, not a feature. I treated them as a nice-to-have for the expense tracker. The moment they worked, every tool needed them. Silent infrastructure is broken infrastructure — if you have to check manually, you will eventually stop checking.

The shared module pattern pays off immediately. One file. Every tool imports it. When I moved from laptop to server, I changed one environment variable. Every notification from every service rerouted automatically.

Mesh VPN is the connective tissue. ntfy alone only works on your LAN. Tailscale's free tier makes it work everywhere. The combination — self-hosted notifications over a mesh VPN — gives you the reach of cloud push services with none of the data routing. Enterprise alternatives like Twingate charge $5-10/user/month for the same connectivity.

SMS is the wrong transport for machine notifications. It's expensive per message, it can't carry action buttons, it can't do rich formatting, and it routes through a telco. HTTP push to a phone app is better in every dimension for automated alerts.


This is Part 5 of my Local AI Architecture series. Part 1 covered dual-model orchestration. Part 2 covered cognitive memory. Part 3 covered context architecture. Part 4 covered vision model selection. Next up: the server that runs all of this — and why a $2,400 dual-GPU build pays for itself.

I build zero-cost AI tools on consumer hardware. The factory runs on Docker, Ollama, and two GPUs. The tools it produces run on nothing.

Top comments (0)