Signal Bot with Local AI
What this article covers
- Building a Signal bot with signal-cli and Ollama.
- Registering a bot number and pairing devices.
- Receiving and sending messages via JSON-RPC and daemon mode.
- Extensions: groups, RAG, attachments, reactions.
- Why Signal is the most privacy-respecting bot platform.
Introduction
Signal is the only mainstream platform with true end-to-end encryption for everything, including bot messages. If you need an AI bot for sensitive communication (journalists, lawyers, internal business matters), Signal is where you’ll end up.
The catch: there’s no official bot API. The standard approach is signal-cli, a Java CLI that implements the Signal protocol and offers a daemon mode with JSON-RPC. Your bot is technically a “Signal client” that functions like any other device.
Common use cases
- Confidential AI assistant: Chats remain E2E-encrypted all the way to your server, ideal for sensitive queries.
- Family or team bot: Internal communication without Meta, Google, or cloud providers.
- Alerting: Monitoring alerts sent E2E-encrypted to your phone (better than email).
- Document assistant: Share confidential documents via Signal and process them locally.
- Editorial contact: Sources can speak anonymously with the AI.
Prerequisites
- A phone number for the bot (landlines work: verification by call; or a second SIM/eSIM)
- Java 17+ (signal-cli is written in Java) or Docker
- Ollama with a model (
llama3.1:8bor similar) - Python for the bot logic
Step 1: Install and register signal-cli
# Binary release (Linux)
wget https://github.com/AsamK/signal-cli/releases/download/v0.13.x/signal-cli-0.13.x-Linux.tar.gz
sudo tar xf signal-cli-*.tar.gz -C /opt
sudo ln -s /opt/signal-cli-*/bin/signal-cli /usr/local/bin/signal-cli
Register the number (requires a captcha link from https://signalcaptchas.org/):
signal-cli -a +4917612345678 register
signal-cli -a +4917612345678 verify 123456
signal-cli -a +4917612345678 updateProfile --name "AI Assistant"
Alternative: Link it as a secondary device to your existing account; the bot will send messages as you (fine for personal assistants, but not for public bots):
signal-cli link -n "Bot-Server"
# returns sgnl:// link → display as QR code on your phone (e.g., with qrencode) and scan
Step 2: Daemon mode with JSON-RPC
signal-cli can run as a JSON-RPC daemon; your Python code communicates over TCP or HTTP:
# TCP daemon
signal-cli -a +4917612345678 daemon --tcp 127.0.0.1:7583
# or HTTP (REST-like)
signal-cli -a +4917612345678 daemon --http 127.0.0.1:8080
JSON-RPC methods (selected): send, receive, listGroups, sendTyping, addReaction, updateProfile.
Step 3: Bot logic with Ollama
The simplest approach is a receive loop via CLI calls (good for getting started):
#!/usr/bin/env python3
import json
import subprocess
import ollama
BOT_NUMBER = "+4917612345678"
MODEL = "llama3.1:8b"
ALLOWED = {"+491701234567"} # Who can use the bot
conversations = {}
client = ollama.Client(host="http://localhost:11434")
def send(recipient, text):
subprocess.run([
"signal-cli", "-a", BOT_NUMBER, "send",
"-m", text, recipient
], check=False)
def ask_ollama(user, text):
history = conversations.setdefault(user, [])
history.append({"role": "user", "content": text})
history = history[-10:]
resp = client.chat(
model=MODEL,
messages=[{"role": "system", "content":
"You are a helpful assistant. Keep your answers brief."}] + history)
answer = resp["message"]["content"]
history.append({"role": "assistant", "content": answer})
conversations[user] = history
return answer
def main():
proc = subprocess.Popen(
["signal-cli", "-a", BOT_NUMBER, "receive", "-t", "-1",
"--output", "json"],
stdout=subprocess.PIPE, text=True)
for line in proc.stdout:
line = line.strip()
if not line:
continue
try:
envelope = json.loads(line)
except json.JSONDecodeError:
continue
msg = envelope.get("envelope", {})
source = msg.get("sourceNumber") or msg.get("source")
data = msg.get("dataMessage")
if not data or source not in ALLOWED:
continue
text = data.get("message")
if not text:
continue
print(f"[{source}] {text}")
answer = ask_ollama(source, text)
# Signal limit ~64k, but short answers are better
send(source, answer[:6000])
if __name__ == "__main__":
main()
This works immediately: polling mode, no webhook needed.
Extensions
Group operation
signal-cli can manage groups. The bot responds in groups only when mentioned or with a prefix:
data = msg.get("dataMessage")
group = data.get("groupInfo") # {"groupId": "...", "type": "..."}
text = data.get("message") or ""
if group:
if not text.startswith("!ai"):
continue
text = text[3:].strip()
# Send the reply to the group:
subprocess.run(["signal-cli", "-a", BOT_NUMBER, "send",
"-g", group["groupId"], "-m", answer])
Create and manage groups via CLI:
signal-cli -a +49... updateGroup -n "Team-AI" -m +491701234567 +491702345678
signal-cli -a +49... listGroups
Reactions and typing indicator
# Show typing
signal-cli -a +49... sendTyping +491701234567
# Send reaction
signal-cli -a +49... sendReaction -e "👍" -a +491701234567 -t 1695123456789 +491701234567
Attachments: send images to vision models
dataMessage.attachments contains file paths: feed images to minicpm-v or llava, audio to Whisper:
for att in data.get("attachments", []):
if att.get("contentType", "").startswith("image/"):
resp = client.chat(model="minicpm-v", messages=[{
"role": "user",
"content": "What is in this image?",
"images": [att["file"]],
}])
send(source, resp["message"]["content"])
RAG like Telegram
Same pattern: embed the message, query Qdrant, inject context into the system prompt. See Telegram Bot RAG and Local RAG.
Deployment
Docker: signal-cli as a daemon
services:
signal-cli:
image: registry.gitlab.com/morph027/signal-cli-daemon:latest
volumes:
- signal_data:/home/.local/share/signal-cli
environment:
- MODE=json-rpc
- SIGNAL_NUMBER=+4917612345678
ports:
- "127.0.0.1:7583:7583"
restart: always
signal-bot:
build: ./bot
restart: always
depends_on: [signal-cli, ollama]
ollama:
image: ollama/ollama:latest
volumes: [ollama_data:/root/.ollama]
volumes:
signal_data:
ollama_data:
systemd without Docker
[Unit]
Description=Signal AI Bot
After=network-online.target
[Service]
Type=simple
User=signalbot
WorkingDirectory=/opt/signal-bot
ExecStart=/usr/bin/python3 bot.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Privacy, the Real Reason for Signal
- Full E2EE: Messages are encrypted between sender and bot server. Signal servers see only metadata, and they minimize that too.
- No ads, no tracking: Signal is a nonprofit organization.
- Open Source: Client, server, and signal-cli are all open source.
- Boundary: The bot itself decrypts on your server, so your rules apply there. Signal still sees metadata (who writes when).
Common Pitfalls
- Registration Captcha: Signal requires captchas during signup. Workaround: register the account on a phone first, then pair it as a linked device.
- Number blocked: If Signal blocks the number (rare): run
signal-cli ... registeragain. - Daemon crashes: signal-cli is Java; on OOM, set
JAVA_OPTS="-Xmx512m". - Receiving stalls: Slow responses in the
receiveloop block the entire loop. Move Ollama calls into threads or async functions. - Sealed Sender: Messages from unknown contacts require trust. Use
signal-cli trust -a <number>for contacts.
Further Reading
- IRC-Coding.de: In-depth programming tutorials on JSON-RPC, subprocess handling, and bot architectures.
- Matrix Bots: Alternative with official bot API and E2EE.
- Telegram Bots: Simplest bot platform.
- Platform Comparison: Which platform for what.
- Privacy: Legal foundations.
- Ollama: Model server.
Key Takeaways:
- Signal bots run through signal-cli (daemon + JSON-RPC), not via an official bot API.
- Only platform with full E2EE, ideal for confidential bots.
- Setup: register a number or pair as a linked device, then run the receive loop.
- Extensions work the same as everywhere: groups, RAG, vision, Whisper.
- For public bots it’s cumbersome: Telegram is simpler, Signal is more private.
FAQ
Is there an official Signal bot API?
Do I need my own phone number?
Are bot messages really encrypted?
Registration fails at the captcha. What do I do?
Does the bot work in groups?
Is signal-cli fast enough?
Signal or Matrix for private bots?
Sources and Further Reading
- signal-cli: CLI and daemon documentation.
- signal-cli JSON-RPC: API reference.
- Signal: Official website.
- IRC-Coding.de: Programming tutorials.


