application
Tintwire¶
active
Self-hosted inbox for structured, interactive operational notifications.
Tintwire is a self-hosted rich-notification inbox for structured, interactive cards. It is a focused alternative to routing operational notifications through a general-purpose chat system.
Work in progress: Tintwire is under active development. Interfaces, configuration, deployment procedures, and database schemas may change without notice. It is not yet recommended for production use without careful review and backups.
Tintwire combines:
- Rich cards with semantic color, fields, tables, images, and authenticated actions.
- An installable PWA with mobile and desktop Web Push, unread badges, and deep links.
- Mattermost and Slack webhook compatibility, including existing
/hooks/{id}URLs. - Bounded bridges for Mattermost bots and custom slash commands.
- Native typed commands, approvals, durable action events, and agent principals with an authenticated MCP surface.
- SQLite for local or single-node use and PostgreSQL for shared multi-node deployments.
Screenshots¶
The inbox combines compatibility webhooks and native structured cards with channel, severity, lifecycle, search, and unread controls.
Channel timelines present filterable card rows alongside ordinary conversation and the channel composer.
Quick start¶
Tintwire is a single Go service that serves its own web client. Start a local SQLite-backed instance with one development webhook:
export TINTWIRE_HOOK_TOKEN=local-development-hook
go run ./cmd/tintwire -hook-id "$TINTWIRE_HOOK_TOKEN"
Publish a compatible notification:
curl -i \
-H 'Content-Type: application/json' \
-d '{"text":"Hello from Mattermost","username":"example-bot"}' \
http://127.0.0.1:8080/hooks/local-development-hook
Open http://127.0.0.1:8080/. The default database is tintwire.db; the
service listens only on loopback unless -listen or TINTWIRE_LISTEN is
set. The webhook token is a secret URL credential and is stored only as a
SHA-256 hash.
A native version 1 card uses the same channel-scoped token:
curl -i \
-H "Authorization: Bearer $TINTWIRE_HOOK_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"version":1,"channel":"#release-lists","title":"Daily release summary","summary":"3 unique releases","severity":"info","source":"release_watcher"}' \
http://127.0.0.1:8080/api/v1/notifications
This unauthenticated reader mode is for loopback development only. Configure a
reader password or OIDC before exposing Tintwire beyond a trusted access
boundary; production authentication also requires the exact browser origin in
TINTWIRE_PUBLIC_URL.
Documentation¶
- Getting started and administration
- Architecture and failure behavior
- Production operations and recovery
- Security and privacy boundaries
- Mattermost compatibility
- Agents and MCP
- Agent conversations and remote control
- Client behavior
- Client validation checklist
- Desktop release policy
- Mattermost channel parity
- Security policy
- Contributing
Synthetic compatibility contracts live in testdata/compat; the interactive
card reference lives in docs/mockups.
Project status¶
Tintwire includes channels and scoped publishing tokens, structured cards, history, search, filters, unread state, realtime delivery, Web Push, Mattermost and Slack compatibility, bot and command bridges, notification lifecycle, authenticated actions, agents, MCP, and a Tauri desktop client.
Deployment topology, ingress, backups, and database failover are intentionally left to the operator.
License¶
MIT. See LICENSE.
Private notification images¶
Native card images can use an authenticated server-side proxy. Configure
TINTWIRE_IMAGE_PROXY_SOURCES (or -image-proxy-sources) as a JSON array:
Reader authentication must be enabled. Matching inbox image URLs become
/api/v1/notifications/{id}/images/{index} without changing stored cards, so
existing notifications also work. Other image origins keep their existing
behavior. Each proxy request checks notification visibility before fetching.
The configured upstream receives the original source Host header, but no
browser credentials. Only a single raster filename after the path prefix is
accepted; query strings, escaped paths, traversal, and redirects are rejected.
The proxy bypasses environment HTTP proxies and limits responses to 8 MiB,
10 seconds, and eight concurrent fetches per node. Responses are sniffed for
WebP/PNG/JPEG/GIF and served with private, no-store; no persistent image copy
is retained. Deleted upstream thumbnails therefore return 404. Restrict the
upstream to the application nodes using your network or origin access rules.
Shared PWA implementation¶
Notification transport, subscription lifecycle and service-worker notification
handling use pwa-kit. Its browser scripts
are embedded through the pinned Go module and served at /pwa-kit/. Upgrade the
module to receive shared fixes; do not copy its implementation into this app.
Keep authentication, subscription ownership/storage, notification policy and worker caching in this app. Follow pwa-kit's adoption checklist when changing these adapters, including real-device verification for iPhone delivery.

