A high-performance, stateless Matrix Adapter service written in Go, implementing the Alkemio Matrix Adapter specification.
This repository contains two core elements:
- Matrix Adapter Service: The Go application that provides the runtime service.
- Matrix Adapter Library: A TypeScript library (
lib/) providing shared payloads and event types for clients of the service.
- Hexagonal Architecture: Clean separation of core domain, ports, and adapters.
- Stateless Design: Designed for horizontal scalability.
- Matrix AppService: Connects to Matrix Homeserver as an Application Service.
- RabbitMQ Integration: Consumes commands and publishes events via AMQP.
- Type-Safe DTOs: Shared contract definitions with TypeScript generation.
- Actor ID Support: Native support for Alkemio Actor IDs (UUIDs) mapped to Matrix IDs.
- Go 1.25+
- Docker & Docker Compose
- Matrix Homeserver (Synapse/Dendrite)
- RabbitMQ
- Node.js 22+ / pnpm 9+ (for Library)
The service is configured via environment variables or a config.yaml file. The environment variables are compatible with the legacy TypeScript service.
| Variable | Description | Default |
|---|---|---|
ENVIRONMENT |
Environment (development, production) | development |
LOGGING_LEVEL_CONSOLE |
Logging level (debug, info, warn, error) | info |
SYNAPSE_SERVER_URL |
URL of the Matrix Homeserver | - |
SYNAPSE_HOMESERVER_NAME |
Matrix Homeserver Name | - |
MATRIX_AS_TOKEN |
AppService Token (as_token) | - |
MATRIX_HS_TOKEN |
Homeserver Token (hs_token) | - |
MATRIX_BOT_ACTOR_ID |
Bot Actor ID (UUID), also used as Matrix localpart | 00000000-0000-0000-0000-000000000000 |
RABBITMQ_URL |
Full AMQP Connection URL | - |
RABBITMQ_HOST |
RabbitMQ Host (if URL not set) | - |
RABBITMQ_PORT |
RabbitMQ Port (if URL not set) | 5672 |
RABBITMQ_USER |
RabbitMQ User (if URL not set) | - |
RABBITMQ_PASSWORD |
RabbitMQ Password (if URL not set) | - |
-
Install Dependencies:
make deps
-
Run the Service:
make run
make buildcd lib
pnpm install
pnpm buildmake docker-builddocker-compose up --build- Linting:
make lint - Testing:
make test - Test Coverage:
make test-coverage - Format:
make fmt - Generate DTOs:
make generate(Updates Go DTOs and generates TypeScript definitions inlib/)
The shared TypeScript library (lib/) is automatically published via GitHub Actions workflows.
| Trigger | Registry | Package Name | Version | Tag |
|---|---|---|---|---|
Git tag (v*) |
npmjs | @alkemio/matrix-adapter-lib |
From tag (e.g., v1.2.3 → 1.2.3) |
latest |
| Pull Request | pkg.pr.new | @alkemio/matrix-adapter-lib |
Commit-based preview URL | — |
| Manual dispatch | pkg.pr.new | @alkemio/matrix-adapter-lib |
Commit-based preview URL | — |
-
Development: PRs that modify
lib/orpkg/dto/automatically publish preview versions via pkg.pr.new for testing. -
Production Release: Create a git tag to publish to npmjs:
git tag v1.2.3 git push origin v1.2.3
-
Install from npmjs (production):
npm install @alkemio/matrix-adapter-lib
-
Install preview (development/testing):
npm install https://pkg.pr.new/alkem-io/matrix-adapter-go/@alkemio/matrix-adapter-lib@{commit-sha}
- npmjs: Uses OIDC trusted publishing (no token required) with provenance attestation.
- pkg.pr.new: No authentication required — uses the pkg.pr.new GitHub App.
The project follows a Hexagonal (Ports & Adapters) architecture:
cmd/: Entrypoints (main application wiring).internal/app/: Application lifecycle, dependency injection, and startup logic.internal/config/: Configuration loading and validation.internal/core/domain: Pure business logic and entities.internal/core/ports: Interfaces defining interactions with the outside world.internal/core/service: Implementation of business use cases.internal/infrastructure: Adapters for external systems (Matrix, RabbitMQ, HTTP).pkg/dto: Data Transfer Objects shared with external consumers (source fortygogeneration).lib/: Shared TypeScript library containing generated DTOs and event types.
The Matrix Adapter uses Actor IDs (UUIDs) to identify users in external APIs.
- Actor ID: The Alkemio agent ID (
agent.id) - used in all RabbitMQ commands and events - Matrix ID: The Matrix user identifier (e.g.,
@uuid:server)
Actor IDs are used directly as Matrix localparts (@actor-uuid:server).
All external APIs (RabbitMQ commands and events) always use Actor IDs.
The Matrix Adapter implements a structured RabbitMQ protocol for communication with the Alkemio Server. See MatrixAdapterProtocol_V3.md for the full specification.
| Category | Command | Topic |
|---|---|---|
| Room | Create Room | communication.room.create |
| Get Room | communication.room.get |
|
| Update Room | communication.room.update |
|
| Delete Room | communication.room.delete |
|
| List Rooms | communication.room.list |
|
| Get Room Members | communication.room.members.get |
|
| Batch Add Member | communication.room.member.batch.add |
|
| Batch Remove Member | communication.room.member.batch.remove |
|
| Space | Create Space | communication.space.create |
| Get Space | communication.space.get |
|
| Update Space | communication.space.update |
|
| Delete Space | communication.space.delete |
|
| List Spaces | communication.space.list |
|
| Batch Add Member | communication.space.member.batch.add |
|
| Batch Remove Member | communication.space.member.batch.remove |
|
| Hierarchy | Set Parent | communication.hierarchy.set_parent |
| Message | Send Message | communication.message.send |
| Get Message | communication.message.get |
|
| Delete Message | communication.message.delete |
|
| Reaction | Add Reaction | communication.reaction.add |
| Remove Reaction | communication.reaction.remove |
|
| Get Reaction | communication.reaction.get |
|
| Thread | Get Thread Messages | communication.thread.messages.get |
| Actor | Sync Actor Profile | communication.actor.sync |
| Read Receipts | Mark Message Read | communication.message.read |
| Get Unread Counts | communication.room.unread_counts.get |
| Event | Topic | Description |
|---|---|---|
| Message Received | communication.message.received |
Emitted when a message is received in a room |
| DM Requested | communication.room.dm.requested |
Emitted when a DM room creation is requested via webhook |
| Reaction Added | communication.reaction.added |
Emitted when a user adds a reaction to a message |
| Reaction Removed | communication.reaction.removed |
Emitted when a user removes a reaction from a message |
| Room Member Left | communication.room.member.left |
Emitted when a user leaves or is kicked from a room |
| Read Receipt Updated | communication.room.receipt.updated |
Emitted when a user's read position is updated |
| Message Edited | communication.message.edited |
Emitted when a message is edited |
| Message Redacted | communication.message.redacted |
Emitted when a message is deleted/redacted |
| Room Created | communication.room.created |
Emitted when a room is created in Matrix |
| Room Member Updated | communication.room.member.updated |
Emitted when a user's membership status changes (join, invite, etc.) |
The adapter supports controlled DM (Direct Message) room creation through a webhook-based flow. This allows Synapse to request approval from the Alkemio Server before creating DM rooms.
┌─────────┐ ┌──────────────┐ ┌─────────────────┐ ┌───────────┐
│ Synapse │ 1. │ Adapter │ 2. │ Alkemio Server │ 3. │ Adapter │
│ Spam │───────▶│ Webhook │───────▶│ (via RabbitMQ)│───────▶│ (via │
│ Checker │ POST │ Handler │ Publish│ │ Command│ RabbitMQ)│
└─────────┘ └──────────────┘ └─────────────────┘ └───────────┘
│
4. Create DM Room │
(type: "direct") ▼
┌───────────┐
│ Matrix │
│Homeserver │
└───────────┘
- Synapse Spam Checker calls the adapter's webhook when a user attempts to create a DM
- Adapter publishes
DMRequestedEventtocommunication.room.dm.requestedtopic - Alkemio Server processes the request and sends
communication.room.createwithtype: "direct" - Adapter creates the DM room in Matrix using existing room creation logic
| Method | Path | Auth |
|---|---|---|
| POST | /_matrix/app/alkemio/dm-request |
Bearer token (HS token) |
{
"inviter": "@550e8400-e29b-41d4-a716-446655440001:matrix.alkemio.org",
"invitee": "@660e8400-e29b-41d4-a716-446655440002:matrix.alkemio.org"
}Success (202 Accepted):
{"status": "accepted"}Error responses use standard HTTP error codes with JSON error messages:
401 Unauthorized: Invalid or missing Bearer token400 Bad Request: Invalid payload or missing fields500 Internal Server Error: Failed to publish event
The Alkemio Server creates DM rooms using the existing communication.room.create command with type: "direct":
{
"alkemio_room_id": "new-uuid-for-dm-room",
"type": "direct",
"name": "DM: User A - User B",
"initial_members": ["actor-uuid-1", "actor-uuid-2"],
"join_rule": "invite"
}All responses follow a standard envelope:
{
"success": true,
"error": null
}Error responses include structured error information:
{
"success": false,
"error": {
"code": "ROOM_NOT_FOUND",
"message": "Room with ID xyz does not exist",
"details": "Optional technical details"
}
}| Code | Description |
|---|---|
INVALID_PARAM |
Request validation failed (invalid payload or parameters) |
ROOM_NOT_FOUND |
Referenced room does not exist |
SPACE_NOT_FOUND |
Referenced space does not exist |
ACTOR_NOT_FOUND |
Referenced actor does not exist |
MATRIX_ERROR |
Matrix SDK/homeserver error |
INTERNAL_ERROR |
Unexpected system error |
NOT_ALLOWED |
Operation not permitted |
The adapter requires an AppService registration file on Synapse. The bot uses MATRIX_BOT_ACTOR_ID as its Matrix localpart, following the same UUID pattern as all other actors.
id: alkemio-matrix-adapter
url: "http://matrix-adapter:8280"
as_token: <your-as-token>
hs_token: <your-hs-token>
sender_localpart: "00000000-0000-0000-0000-000000000000" # Must match MATRIX_BOT_ACTOR_ID
namespaces:
users:
# Bot user - exclusive (security: prevents impersonation)
- exclusive: true
regex: "@00000000-0000-0000-0000-000000000000:.*"
# Regular UUID users - NOT exclusive so they can login via OIDC/Element
- exclusive: false
regex: "@[0-9a-fA-F-]{36}:.*"
aliases:
- exclusive: true
regex: "#[0-9a-fA-F-]{36}:.*" # Room aliases
rate_limited: falseImportant:
- The
sender_localpartmust match theMATRIX_BOT_ACTOR_IDenvironment variable - The bot user namespace should be
exclusive: trueto prevent impersonation attacks