- Rust 100%
| src | ||
| tests | ||
| .gitignore | ||
| .gitleaksignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
matrix-relay-bot
Rust Matrix E2EE relay with two integrations:
Matrix mention -> authenticated webhook JSON
Authenticated POST /send -> Matrix text or media event
The consuming environment owns deployment, credentials, and runtime state. The bot logs into an existing Matrix account once to create its own device and crypto store. Normal service startup restores that session; it never receives the Matrix password.
Requirements
- Rust 1.93 or newer
- Cargo
- OpenSSL and pkg-config
- An existing Matrix account joined to the required encrypted rooms
Build and test
cargo build --locked
cargo test --all-features
cargo clippy --all-targets --all-features -- -D warnings
First initialization
Store the Matrix password in a mode-private file, then run:
matrix-relay-bot init \
--matrix-homeserver-url https://matrix.example.test \
--matrix-bot-user-id @relay:example.test \
--matrix-device-id N8NRELAY02 \
--matrix-device-display-name 'n8n Matrix Relay (Rust)' \
--matrix-storage-dir /var/lib/matrix-relay-bot/rust \
--matrix-password-file /run/credentials/matrix-relay-bootstrap.service/password
init performs exactly one password login for the fixed device ID, creates the Matrix SDK SQLite crypto store, performs one initial sync, and atomically commits:
/var/lib/matrix-relay-bot/rust/
├── crypto/
├── outbox.sqlite
└── session.json
Running init again accepts complete matching state without another login. Partial state, identity drift, or homeserver drift fails. A failed first attempt leaves the sibling .rust.init directory for diagnosis; remove it only after determining why initialization failed.
Normal service
serve requires no Matrix password, access-token file, declarative device-key pins, or initial cursor:
WEBHOOK_AUTH_TOKEN='matrix-to-n8n-secret' \
matrix-relay-bot serve \
--matrix-homeserver-url https://matrix.example.test \
--matrix-bot-user-id @relay:example.test \
--matrix-device-id N8NRELAY02 \
--matrix-storage-dir /var/lib/matrix-relay-bot/rust \
--matrix-room-allowlist '!room:example.test' \
--n8n-webhook-url https://n8n.example.test/webhook/matrix \
--bot-send-token-file /run/credentials/matrix-relay-bot.service/bot-send-token \
--operator-token-file /run/credentials/matrix-relay-bot.service/operator-token \
--bot-send-bind-address 127.0.0.1 \
--bot-send-port 8080 \
--max-download-bytes 52428800
MATRIX_ROOM_ALLOWLIST is optional. When omitted, every joined room is eligible; startup fails if no joined room exists. Matrix SDK encrypts messages in encrypted rooms and preserves normal Matrix behavior in unencrypted rooms. The durable outbox resumes from its stored cursor and does not replay acknowledged events.
HTTP API
Health is unauthenticated:
curl --fail http://127.0.0.1:8080/healthz
Send text to Matrix with the exact BOT_SEND_TOKEN value in the Authorization header:
curl --fail \
-H 'Authorization: n8n-to-matrix-secret' \
-H 'Content-Type: application/json' \
--data '{"roomId":"!room:example.test","message":"hello"}' \
http://127.0.0.1:8080/send
Successful response:
{"eventId":"$event:example.test"}
For media, replace message with mediaUrl, kind (image, video, audio, or file), and filename. Optional reply fields are replyToEventId, replyToSenderId, and replyToBody.
Webhook
A qualifying decrypted mention is posted to N8N_WEBHOOK_URL:
{
"text": "cleaned mention text",
"originalContent": "@relay:example.test do this",
"roomId": "!room:example.test",
"eventId": "$event:example.test",
"senderId": "@alice:example.test",
"timestamp": 1770000000000,
"formattedBody": null
}
When WEBHOOK_AUTH_TOKEN is set, the webhook request uses Authorization: Bearer <token>. Successful delivery is durable. HTTP 429, HTTP 5xx, and transport failures receive bounded retries; other non-success responses are discarded as permanent failures.
License
MIT