Β
A simple, self-hostable relay server that acts as a dumb encrypted mailbox. Any application can use it to pass encrypted data between users. The server never sees plaintext β all encryption/decryption happens client-side.
- Encrypted Message Relay: Store and retrieve opaque encrypted payloads via simple REST endpoints
- Self-Hostable: Single binary, embedded SQLite, zero external dependencies
- Configurable TTL: Auto-purge messages after N days, or set to 0 for permanent storage
- Rate Limiting: Built-in IP-based rate limiting to prevent abuse
- API Key Auth: Optional API key authentication for private relays
- Docker Ready: Dockerfile and Docker Compose included
- Go 1.22+
- SQLite (embedded, WAL mode) via
mattn/go-sqlite3 net/http(standard library)
- Docker + Docker Compose
- GitHub Actions CI/CD
graph TD
Client["Client / SDK"] -->|HTTP| MW1["API Key Middleware"]
MW1 --> MW2["Rate Limiter"]
MW2 --> Mux["net/http ServeMux"]
Mux -->|"POST /api/messages"| H1["Create Message"]
Mux -->|"GET /api/messages/:address"| H2["Get by Address"]
Mux -->|"DELETE /api/messages/:id"| H3["Delete Message"]
Mux -->|"GET /health"| H4["Health Check"]
H1 & H2 & H3 --> Store["Store Interface"]
Store --> SQLite["SQLiteStore (WAL mode)"]
BG["Hourly Purge Goroutine"] --> Store
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/messages |
Store a new encrypted message |
GET |
/api/messages/:address |
Fetch all messages for a wallet address |
DELETE |
/api/messages/:id |
Delete a specific message |
GET |
/health |
Server health check |
curl -X POST http://localhost:3000/api/messages \
-H "Content-Type: application/json" \
-d '{"to": "0xRecipient", "from": "0xSender", "payload": "encrypted_base64_data"}'curl http://localhost:3000/api/messages/0xRecipientcurl -X DELETE http://localhost:3000/api/messages/<message-id>- ThruBox Server β This repository (relay server)
- ThruBox Client β TypeScript SDK
- Go 1.22+ with CGo enabled (required for SQLite)
- GCC (for compiling go-sqlite3)
- Docker (optional, for containerized deployment)
git clone https://github.com/AOSSIE-Org/ThruBox-Server.git
cd ThruBox-Servergo mod downloadgo build -o relay-server ./cmd/relay
./relay-serverThe server starts on http://localhost:3000 with a SQLite database that auto-creates at ./data/relay.db.
docker compose up -dThruBox reads config.yaml from the working directory. Set RELAY_CONFIG_PATH
to load it from anywhere else:
RELAY_CONFIG_PATH=/etc/relay/config.yaml ./relay-serverThe image sets this for you and ships the file at /etc/relay/config.yaml, so
you can mount your own over it:
docker run -v ./my-config.yaml:/etc/relay/config.yaml:ro ghcr.io/aossie-org/thrubox-serverIf no config file is found at the resolved path the server logs a warning at startup and runs on built-in defaults rather than failing. Precedence is built-in defaults β the selected YAML file β environment variables, so an environment variable always wins over the file.
go test ./...Tests live beside the code they cover. See
CONTRIBUTING.mdbefore submitting a PR that adds functionality without tests.
Edit config.yaml or use environment variables:
| Setting | YAML Key | Env Variable | Default |
|---|---|---|---|
| Config file path | β | RELAY_CONFIG_PATH |
config.yaml (/etc/relay/config.yaml in the Docker image) |
| Server port | server.port |
RELAY_SERVER_PORT |
3000 |
| Server port (fallback) | β | PORT |
unset β used when RELAY_SERVER_PORT is unset or empty |
| Server host | server.host |
RELAY_SERVER_HOST |
0.0.0.0 |
| Storage path | storage.path |
RELAY_STORAGE_PATH |
./data/relay.db |
| Message TTL | messages.ttl_days |
RELAY_MESSAGES_TTL_DAYS |
7 (0 = forever) |
| Max payload | messages.max_payload_size |
RELAY_MESSAGES_MAX_PAYLOAD_SIZE |
524288 (500KB) |
| Rate limit | security.rate_limit |
RELAY_SECURITY_RATE_LIMIT |
30 req/min/IP |
| API key | security.api_key |
RELAY_SECURITY_API_KEY |
`` (disabled) |
| CORS origins | security.allowed_origins |
RELAY_SECURITY_ALLOWED_ORIGINS |
`` (CORS disabled) |
By default the relay serves no CORS headers, so a browser calling it from another origin is blocked at the preflight. There are two ways to run a browser client:
1. Reverse proxy (no relay configuration). Put the relay behind a
same-origin path in your own app β a Vercel rewrite, an nginx location, a
Vite server.proxy entry. The request is never cross-origin, so CORS never
applies. This is the setup the ThruBox client docs assume.
2. Direct calls with an origin allowlist. List the origins you want to serve and browsers can call the relay directly, no proxy needed:
security:
allowed_origins:
- "https://app.example.com"
- "http://localhost:5173"or via the environment, comma-separated:
RELAY_SECURITY_ALLOWED_ORIGINS="https://app.example.com,http://localhost:5173"When an origin is allowed, the relay answers preflights and returns
Access-Control-Allow-Origin for that origin, Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS, and Access-Control-Allow-Headers: Content-Type
β plus X-API-Key when security.api_key is set.
Notes:
- Entries must be full origins (
https://host[:port]), with no path. A bare hostname or a URL with a path is rejected at startup rather than silently never matching. "*"allows any origin. It cannot be combined with specific origins, and it is a poor fit for a relay with no API key configured β anything on the web can then read and write messages from a browser.- Credentialed CORS is not supported. The relay authenticates with the
X-API-Keyheader, not cookies, soAccess-Control-Allow-Credentialsis never sent. - An allowlisted origin still has to satisfy
security.api_keyand the rate limiter. CORS controls which origins a browser will let read a response; it is not authentication.
Deploying to a managed platform? Render, Railway, Heroku and Cloud Run inject a
PORTvariable and expect the process to bind to it. ThruBox reads it automatically, so no extra configuration is needed.RELAY_SERVER_PORTstill takes precedence whenever it is set to a non-empty value, so setting it to""(as an emptydocker-composeentry does) falls through toPORT.
β Don't forget to star this repository if you find it useful! β
Thank you for considering contributing to this project! Contributions are highly appreciated and welcomed. To ensure smooth collaboration, please refer to our Contribution Guidelines.
See MAINTAINERS.md for the full list of Mentors and Maintainers for this repository.
This project is licensed under the GNU General Public License v3.0. See the LICENSE file for details.
Thanks a lot for spending your time helping ThruBox grow. Keep rocking π₯
Β© 2025 AOSSIE