Four Ways to Run a Signal Bot: Picking Your Speed and Memory Tradeoff
You've probably been there: you want to automate something on Signal—send a notification, handle a group message, or build a small bot—but the official client is a mobile app, and there's no clean HTTP endpoint to hit. You could hack something together with the CLI, but every call spins up a Java process, which is slow and clunky. That's exactly the gap this project fills.
Dockerized Signal Messenger REST API wraps signal-cli in a small Docker container and exposes a REST interface for the most common operations. Register a number, send messages, manage groups, link devices—all over HTTP, with a choice of four execution modes so you can balance speed against memory usage.
What It Does
At its core, this is a thin HTTP layer over signal-cli, which is itself a command-line interface for the Signal Messenger protocol. The project packages that up into a Docker image, so you run one container and get a REST API on port 8080.
The functionality is practical and covers the essentials:
- Register a number and verify it with the SMS code
- Send messages with attachments to multiple recipients or a group
- Receive messages
- Link devices (register the container as a secondary device)
- Create, list, and remove groups
- List, serve, and delete attachments
- Update your profile
The architecture is straightforward: you mount a local directory into the container to persist your Signal registration, so you can delete and recreate the container without re-registering. The README calls this out explicitly—it's a nice touch for anyone who's lost a bot registration to a container rebuild.
Why It's Cool
The most interesting part isn't the REST API itself—it's the four execution modes. This is a thoughtful design decision that addresses a real pain point with Java-based CLI tools.
Here's the problem: signal-cli is a Java application. In the default normal mode, every REST request spawns a new JVM, which means startup latency on every single call. It works, but it's the slowest option.
The project gives you three alternatives:
-
nativemode uses a GraalVM-compiled binary (signal-cli-native) for each request. Much lower latency and memory usage per call, though the README notes it's less stable since GraalVM is still experimental. Also, it falls back tonormalonarmv7platforms. -
json-rpcmode spawns a single JVM-basedsignal-clidaemon that stays running. This is usually the fastest, but that persistent JVM means higher memory usage. -
json-rpc-nativemode combines the best of both: the native binary running as a daemon. Fast, with normal memory usage.
The README includes a comparison table that sums it up nicely—speed increases as you move from normal to native to json-rpc to json-rpc-native, while memory stays normal except for json-rpc, which is increased.
That's a refreshingly honest tradeoff. Instead of pretending there's one perfect mode, the project lets you pick based on your constraints. Running on a Raspberry Pi with limited RAM? Go native. Need maximum throughput? Try json-rpc. The choice is yours, and the README is upfront about the tradeoffs.
How to Try It
Getting started is quick. First, create a directory for your configuration:
$ mkdir -p $HOME/.local/share/signal-api
Then start the container. This example uses native mode:
$ sudo docker run -d --name signal-api --restart=always -p 8080:8080 \
-v $HOME/.local/share/signal-api:/home/.local/share/signal-cli \
-e 'MODE=native' bbernhard/signal-cli-rest-api
Now you need to link your Signal number. Open http://localhost:8080/v1/qrcodelink?device_name=signal-api in your browser, then on your phone go to Settings > Linked devices and scan the QR code.
Once that's done, send a test message. Replace +4412345 with your number and +44987654 with the recipient's:
$ curl -X POST -H "Content-Type: application/json" 'http://localhost:8080/v2/send' \
-d '{"message": "Test via Signal API!", "number": "+4412345", "recipients": [ "+44987654" ]}'
To switch execution modes, just change the MODE environment variable. The README shows an example running on port 9922 with a different config path, but the pattern is the same—normal, native, json-rpc, or json-rpc-native.
Final Thoughts
This project is best for developers who need a Signal integration without building a full client from scratch. The REST API covers the common operations, and the execution modes give you real control over performance characteristics—a level of consideration you don't often see in hobbyist tooling.
The honest tradeoffs are the highlight. The README doesn't oversell native mode; it tells you it's experimental and might be less stable. That kind of transparency is refreshing. If you need to automate Signal messages and you're comfortable running a Docker container, this is a solid starting point. The documentation also points to a fuller API reference, so there's room to grow beyond the basics.
Follow @githubprojects for more developer tools and open source projects.