aivuk/talk-to-me
A minimal web chat bridge. Visitors on your website send messages; you receive them via Matrix and reply from Element (or any Matrix client). Each visitor gets a dedicated Matrix room.
talk-to-me
A minimal web chat bridge. Visitors on your website send messages; you receive them via Matrix and reply from Element (or any Matrix client). Each visitor gets a dedicated Matrix room.
Requirements
- A Linux server with Apache and a valid TLS certificate
- Node.js v22+ (recommend nvm)
- A Matrix homeserver — this setup uses tuwunel, a lightweight Rust-based server
- Element (or any Matrix client) on your phone/desktop to receive and reply to messages
1. Matrix homeserver (tuwunel)
Install
Download the latest .deb from the tuwunel releases page and install it:
wget https://github.com/girlbossceo/tuwunel/releases/latest/download/tuwunel_amd64.deb
sudo dpkg -i tuwunel_amd64.debConfigure
Edit /etc/tuwunel/tuwunel.toml:
[global]
server_name = "yourdomain.com"
database_path = "/var/lib/tuwunel"
address = "127.0.0.1"
port = 8008
allow_registration = true # set to false after creating accounts
allow_federation = false
[global.well_known]
client = "https://matrix.yourdomain.com"
server = "matrix.yourdomain.com:443"The
server_namecontrols user IDs (e.g.@you:yourdomain.com). The homeserver itself runs atmatrix.yourdomain.comvia Apache proxy — see section 4.
Start
sudo systemctl enable --now tuwunelCreate accounts
# Owner account (you)
curl -X POST http://localhost:8008/_matrix/client/v3/register \
-H 'Content-Type: application/json' \
-d '{"username":"you","password":"your-password","kind":"user"}'
# Bot account (the bridge)
curl -X POST http://localhost:8008/_matrix/client/v3/register \
-H 'Content-Type: application/json' \
-d '{"username":"bot","password":"your-bot-password","kind":"user"}'Then disable registration in tuwunel.toml (allow_registration = false) and restart:
sudo systemctl restart tuwunel2. Backend
Clone and install
git clone https://github.com/youruser/talk-to-me.git
cd talk-to-me/backend
npm installConfigure
Copy the example env file and fill in your values:
cp ../.env.example ../.env
nano ../.envMATRIX_HOMESERVER_URL=http://127.0.0.1:8008
MATRIX_BOT_USER=@bot:yourdomain.com
MATRIX_BOT_PASSWORD=your-bot-password
MATRIX_OWNER_USER=@you:yourdomain.comLock down the file:
chmod 600 ../.envCreate the data directory
mkdir -p ../dataRun as a systemd service
Copy the service file and edit paths if needed:
sudo cp /path/to/talk-to-me.service /etc/systemd/system/talk-to-me.service
sudo systemctl daemon-reload
sudo systemctl enable --now talk-to-meExample service file (/etc/systemd/system/talk-to-me.service):
[Unit]
Description=talk-to-me Matrix chat bridge
After=network.target tuwunel.service
Requires=tuwunel.service
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser/talk-to-me/backend
EnvironmentFile=/home/youruser/talk-to-me/.env
ExecStart=/home/youruser/.nvm/versions/node/v22.18.0/bin/node index.js
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetAdjust the
nodepath to match your installation (which nodeornode --versionto verify).
3. Frontend
Copy the frontend files to your web root:
sudo cp -r frontend/ /var/www/yoursite/The chat lives in frontend/index.html. The Geist font is included in frontend/fonts/Geist.woff2.
4. Apache configuration
You need two virtual hosts: one for the main site (yourdomain.com) and one for the Matrix homeserver (matrix.yourdomain.com).
yourdomain.com — proxy the API and Matrix well-known
Add inside the :443 VirtualHost:
# Chat backend API
ProxyPass /api http://127.0.0.1:3000/api
ProxyPassReverse /api http://127.0.0.1:3000/api
# Matrix well-known delegation (lets clients discover matrix.yourdomain.com)
ProxyPass /.well-known/matrix http://127.0.0.1:8008/.well-known/matrix
ProxyPassReverse /.well-known/matrix http://127.0.0.1:8008/.well-known/matrixmatrix.yourdomain.com — proxy the Matrix homeserver
<VirtualHost *:443>
ServerName matrix.yourdomain.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
ProxyPass / http://127.0.0.1:8008/
ProxyPassReverse / http://127.0.0.1:8008/
</VirtualHost>Enable required modules and reload:
sudo a2enmod proxy proxy_http
sudo systemctl reload apache2Owner commands
Send these as plain text messages from Element in any visitor's room:
| Command | Effect |
|---|---|
rename: "Name" |
Renames the room and shows the name as a sticky header in the visitor's chat |
info |
Re-sends the visitor's IP, location, browser, and device to the room |
dark |
Switches the visitor's UI to dark mode |
light |
Switches the visitor's UI to light mode |
block |
Blocks the visitor's IP — they see "you are blocked" and cannot send new messages |
unblock |
Unblocks the visitor's IP — their UI restores automatically within ~3 seconds |
Directory structure
talk-to-me/
├── .env # credentials (never commit — see .env.example)
├── .env.example # template
├── README.md
├── ARCHITECTURE.md # detailed technical documentation
├── backend/
│ ├── index.js # Express server + Matrix sync loop
│ ├── matrix.js # Matrix REST API wrapper
│ ├── db.js # SQLite schema and queries
│ └── package.json
├── frontend/
│ ├── index.html # entire frontend (single file)
│ └── fonts/
│ └── Geist.woff2 # self-hosted variable font (MIT, Vercel)
└── data/
└── sessions.db # SQLite database (auto-created, gitignored)
Updating
git pull
cd backend && npm install
sudo systemctl restart talk-to-me
sudo cp ../frontend/index.html /var/www/yoursite/index.html