Skip to content

application

Tintwire

active

Self-hosted inbox for structured, interactive operational notifications.

GitHub repository

Source: README.md
Commit: 39e0fa5eb3b04f8729bbdb656298a09887bda196
Source updated:  ·  Edit this page

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

Tintwire signal inbox populated with synthetic operational notifications

The inbox combines compatibility webhooks and native structured cards with channel, severity, lifecycle, search, and unread controls.

Tintwire release-summary channel displaying a synthetic structured card

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

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:

[{"origin":"https://camera.example","upstream":"http://192.168.1.10","path_prefix":"/thumb-"}]

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.