Includes: - Backend services: ingestion (:8001), weather API (:8002), gateway (:8003), billing (:8004) with BTCPay integration - Shared asyncpg pool, TimescaleDB hypertable, Redis, Mosquitto MQTT - React frontend: Dashboard (MapLibre) and Messaging (chat UI) - Bridge daemon for Pi + Meshtastic (Serial/TCP T-Deck support) - Production Docker Compose, Nginx reverse proxy, ops scripts - DEPLOY.md with step-by-step deployment guide
6.2 KiB
6.2 KiB
Messaging Gateway Architecture
The Messaging Gateway is the bridge between the internet (web users) and the Meshtastic mesh. It is the primary monetization surface for the project.
Business Rules
- The mesh is open: Anyone with a Meshtastic device can join the Kosmo mesh and send/receive messages locally for free.
- The gateway is gated: Sending a message from the internet to the mesh requires an active subscription.
- Authorization granularity:
- Network-level: Subscriber can send to any node reachable through the gateway.
- Node-level: Subscriber can send only to specific whitelisted nodes (e.g., family members).
- Future: Group-level access for organizations.
Gateway Flow: Web → Mesh
User (Web Browser)
│
▼
┌──────────────┐
│ Web API │ <-- Validates JWT, checks subscription status
│ /messages │
└──────┬───────┘
│
▼
┌──────────────┐
│ Billing │ <-- Confirms subscriber has active plan & quota remaining
│ Service │
└──────┬───────┘
│
▼
┌──────────────┐
│ Message │ <-- Writes message to outbound queue (RabbitMQ / Redis)
│ Gateway │ Topic: `mesh.outbound.{node_id}`
└──────┬───────┘
│ MQTT / TLS
▼
┌──────────────┐
│ Infrastructure│ <-- Bridge daemon reads queue
│ Node │
└──────┬───────┘
│ Serial / protobuf API
▼
┌──────────────┐
│ Meshtastic │ <-- Broadcasts text message to target node ID
│ Radio │
└───────────────┘
Gateway Flow: Mesh → Web
Replies and inbound messages from the mesh to a subscriber:
Meshtastic Radio (any node)
│
▼
┌──────────────┐
│ Infrastructure│ <-- Receives mesh message
│ Node │
└──────┬───────┘
│
▼
┌──────────────┐
│ Bridge │ <-- Publishes to `kosmo/mesh/inbound`
│ Daemon │
└──────┬───────┘
│
▼
┌──────────────┐
│ Message │ <-- Matches sender node ID to subscriber inboxes
│ Gateway │
└──────┬───────┘
│
▼
┌──────────────┐
│ Web API │ <-- Stores in user's inbox, sends push notification
│ Inbox │
└───────────────┘
Subscription Models
Plan Tiers (Example)
| Tier | Price | Messages/Month | Scope | Features |
|---|---|---|---|---|
| Free | $0 | 5 (inbound only) | Inbox | Receive replies, view weather |
| Wanderer | $5/mo | 50 | Network | Send to any node |
| Guardian | $12/mo | 500 | Node-level | Manage up to 5 linked nodes |
| Sanctuary | $50/mo | Unlimited | Network + API | Bulk messaging, webhook access |
All paid plans are processed through the Church of Kosmo's self-hosted BTCPay Server at pay.cqre.net.
Authorization Check
When a user attempts to send a message:
def can_send(user: User, target_node_id: str) -> bool:
subscription = user.active_subscription()
if not subscription or subscription.is_expired():
return False
if subscription.plan == "network":
return True
if subscription.plan == "node_level":
return user.allowed_nodes.filter(mesh_node_id=target_node_id).exists()
return False
Message Queue Schema
Outbound (Cloud → Mesh)
{
"message_id": "uuid-v4",
"sender_user_id": "uuid-v4",
"target_node_id": "!a1b2c3d4",
"text": "Hello from the web!",
"priority": "normal",
"max_hops": 7,
"want_ack": true,
"created_at": "2026-04-12T09:20:00Z",
"retry_count": 0
}
Inbound (Mesh → Cloud)
{
"message_id": "uuid-v4",
"source_node_id": "!a1b2c3d4",
"gateway_node_id": "!gateway01",
"text": "Reply from the woods",
"hop_count": 3,
"rssi": -90,
"snr": 8.5,
"received_at": "2026-04-12T09:25:00Z"
}
Rate Limiting & Anti-Spam
- Per-user: Max 1 message per 10 seconds, burst of 5
- Per-subscription tier: Enforced monthly quotas
- Per-target-node: Max 10 web messages per hour (to prevent harassment)
- Content filtering: Basic profanity/spam filter on the gateway
- Blocklist: Users and nodes can block each other
Delivery Tracking
The gateway tracks message state:
PENDING -> QUEUED -> TRANSMITTED -> DELIVERED (ACK received)
|
+-> FAILED (max retries exceeded)
Users see delivery status in the messaging UI:
- Single checkmark: Queued
- Double checkmark: Transmitted by gateway
- Blue double checkmark: Delivered (ACK from target node)
Billing Integration
The gateway relies on the Billing Service to enforce subscriptions. The billing service:
- Creates invoices via BTCPay Server Greenfield API
- Listens to BTCPay webhooks for payment confirmation
- Manages subscription validity periods and quotas in PostgreSQL
- Deactivates old subscriptions and resets quotas on successful payment
Security Considerations
- Authentication: JWT-based auth for web users, API keys for bridge daemons
- Encryption: Mesh messages are encrypted with the channel key. The bridge daemon does not decrypt content; it only forwards the encrypted payload.
- Privacy: The gateway logs message metadata (sender, recipient, timestamp, size) but does not log message content.
- Node Impersonation: Web messages are tagged with a special prefix or sender ID indicating they originated from the gateway, preventing spoofing of local mesh nodes.
Fallback Behavior
If no infrastructure node is currently online:
- Outbound messages remain queued for up to 24 hours
- Users are notified that delivery is delayed
- If the queue expires, the message is marked as failed and the user's quota is refunded