Most bot and notification setups rely on Telegram or Signal. Both are fine, but they require trusting third-party infrastructure with your metadata. After reading about OpenClawd and noticing the Telegram dependency, I decided to set up something more private.

SimpleX is a messaging protocol with no user identifiers - no phone numbers, no usernames, no accounts. Combined with a self-hosted relay server, you get end-to-end encrypted messaging where you control the infrastructure.

The code is on GitHub: AusDavo/simplex-cli-docker

What I’m Building

  • SimpleX CLI running in Docker with WebSocket API
  • Connected to a self-hosted SMP relay server
  • Accessible from other containers for bot integrations
  • Private notifications to your phone

Prerequisites

  • Docker and Docker Compose
  • A SimpleX SMP relay server (optional but recommended)
  • SimpleX app on your phone

The Docker Setup

Dockerfile

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    curl \
    ca-certificates \
    libgmp10 \
    expect \
    socat \
    && rm -rf /var/lib/apt/lists/*

RUN useradd -m -s /bin/bash simplex && \
    mkdir -p /home/simplex/.simplex /home/simplex/bin && \
    chown -R simplex:simplex /home/simplex

WORKDIR /home/simplex
USER simplex

RUN curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/stable/install.sh | bash

ENV PATH="/home/simplex/.local/bin:${PATH}"

USER root
COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh
COPY --chmod=755 init-user.exp /usr/local/bin/init-user.exp
USER simplex

ENV SIMPLEX_USER=SimplexBot

VOLUME ["/home/simplex/.simplex"]
EXPOSE 5225

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["-p", "5226"]

The Tricky Part: User Initialization

SimpleX CLI requires an interactive terminal to create the initial user profile. In a Docker container without a TTY, this fails. The solution is an expect script that handles the interactive prompts:

init-user.exp:

#!/usr/bin/expect -f

set timeout 30
set username [lindex $argv 0]

spawn simplex-chat

expect {
    "display name:" {
        send "$username\r"
    }
    "No user profiles found" {
        expect "display name:"
        send "$username\r"
    }
    timeout {
        puts "Timeout waiting for prompt"
        exit 1
    }
}

expect {
    -re "Welcome.*$username" {
        puts "User created successfully"
    }
    -re ">" {
        puts "User created"
    }
    timeout {
        puts "Timeout after username entry"
    }
}

send "/quit\r"
expect eof

Another Gotcha: Localhost Binding

The SimpleX CLI WebSocket server binds to 127.0.0.1, not 0.0.0.0. This means other containers can’t connect to it. The fix is socat:

entrypoint.sh:

#!/bin/bash
set -e

DB_DIR="/home/simplex/.simplex"
DB_FILE="$DB_DIR/simplex_v1_chat.db"
USER_NAME="${SIMPLEX_USER:-SimplexBot}"
INTERNAL_PORT=5226
EXTERNAL_PORT=5225

if [ -f "$DB_FILE" ]; then
    echo "Database exists, starting in API mode..."
else
    echo "First run detected. Creating user profile: $USER_NAME"
    /usr/local/bin/init-user.exp "$USER_NAME" || true

    if [ ! -f "$DB_FILE" ]; then
        echo "Warning: Database file not found, but continuing..."
    else
        echo "User profile created successfully!"
    fi
fi

echo "Starting socat proxy (0.0.0.0:$EXTERNAL_PORT -> 127.0.0.1:$INTERNAL_PORT)..."
socat TCP-LISTEN:$EXTERNAL_PORT,fork,reuseaddr TCP:127.0.0.1:$INTERNAL_PORT &

# Build command args
ARGS=("$@")

# Add SMP server if configured
if [ -n "$SMP_SERVER" ]; then
    echo "Using SMP server: $SMP_SERVER"
    ARGS+=("-s" "$SMP_SERVER")
fi

echo "Starting SimpleX CLI in API mode on internal port $INTERNAL_PORT..."
exec simplex-chat "${ARGS[@]}"

Docker Compose

services:
  simplex-cli:
    build: .
    container_name: simplex-cli
    expose:
      - "5225"
    volumes:
      - simplex-data:/home/simplex/.simplex
    restart: unless-stopped
    env_file:
      - path: .env
        required: false
    environment:
      - SIMPLEX_USER=${SIMPLEX_USER:-SimplexBot}
      - SMP_SERVER=${SMP_SERVER:-}

volumes:
  simplex-data:

Configure via .env:

SIMPLEX_USER=SimplexBot
SMP_SERVER=smp://YOUR_FINGERPRINT@your-relay.example.com

For external networks, use docker-compose.override.yml:

services:
  simplex-cli:
    networks:
      - your-network

networks:
  your-network:
    external: true

Configuring Your Relay

If you’re running your own SMP server, configure the CLI to use it:

docker exec -it simplex-cli simplex-chat

Then:

/smp smp://YOUR_FINGERPRINT@your-relay.example.com

Creating Your Contact Address

Generate an address that uses your relay:

/address

This produces a link you can convert to a QR code:

qrencode -t ANSIUTF8 "https://your-relay.example.com/a#..."

Scan with your SimpleX app to connect.

WebSocket API

The CLI exposes a WebSocket API on port 5225 for programmatic access:

Message format:

{"corrId": "1", "cmd": "/users"}

Response:

{"corrId": "1", "resp": {...}}

Sending a message:

{"corrId": "2", "cmd": "@'Contact Name' Hello from the bot!"}

Privacy Comparison

Service Message Content Metadata Infrastructure
Telegram Visible to Telegram Fully exposed Telegram servers
Signal E2E encrypted Phone numbers exposed Signal servers
SimpleX (public) E2E encrypted Minimal SimpleX servers
SimpleX (self-hosted) E2E encrypted You control it Your servers

Use Cases

  • Server monitoring alerts
  • CI/CD notifications
  • Home automation events
  • Security alerts (failed logins, etc.)
  • Personal reminders
  • Anything you’d use Telegram bots for, but private

Conclusion

It takes more effort than spinning up a Telegram bot, but if you care about metadata privacy, self-hosted SimpleX is worth it. Your notifications flow through infrastructure you control, with end-to-end encryption and no user identifiers.

The WebSocket API makes it straightforward to integrate with tools like n8n, Home Assistant, or custom scripts.


Running SimpleX CLI v6.4.8.0 on Docker with Ubuntu 22.04 base image.