Sending Signal messages
Introduction
Something I have always wanted is the ability to send messages programmatically to my phone. It is such a useful thing to have - being able to send out notifications myself via code and since it is a text, I generally don’t miss it.
Many many years ago when Orkut was still a thing - there was an app that would send out real sms based text messages of your horoscope daily. I absolutely loved getting my information over text and have wanted to recreate that ever since.
Possible approaches
Twilio/MessageBird SMS APIs
Twilio is synonymous with sending text messages via code. MessageBird is another company that provides a similar service for a slightly lower price.
This is the best option to get text messages but didn’t use this since I don’t want to pay for the service.
Emails
Another adjacent way is to send an email, which is more or less free using either sendgrid or connecting directly to the mail server.
Did not use this option either since I don’t want the message getting buried in my inbox inside gmail or outlook.
Push notification
I could send the message via a push notification using OneSignal or Ntfy.sh - The only thing is I don’t want to install an app that exists solely to receive push notifications. Notifications are also ephemeral and have no history
Facebook/Instagram/Whatsapp
I would have assumed this would be the easiest way to communicate but the APIs are quite gated now
Using Signal
Turns out there exists a repo [ https://github.com/bbernhard/signal-cli-rest-api
] that provides Signal’s messaging rest api as a docker container ready to be deployed. Of course, there is no free lunch and the catch is that you need a spare number lying around to actually register the container as a ‘user’ but at least the messaging is free and it shows up [with history tracked] in my Signal app which is my main communication app.
The docker container is run on a server and exposed [and password protected] via nginx. Whenever a message needs to be sent an HTTP request is made to /v2/send
with the following json payload
1 | { |
Setup
Getting it all setup took some work and tinkering, which is why I am writing this blog post so others can save time, specially around the captcha and registering of a number.
Getting a number
The very first thing needed is a phone number that can be used to register as a user with Signal. I am using the TextNow app to get the number. It is important that this number can receive incoming SMS messages
Deploying the Signal Docker container
Next we need to actually run the Signal Docker container on a server. I run it as part of my docker compose deployment on my DigitalOcean server [Get $200 in credit over 60 days => https://m.do.co/c/b5f565690240
] using the following config
1 | signal: |
Exposing and password protecting the Signal API server
The server is exposed to the outside world via Nginx - the config I am using is as follows
1 | # nginx is part of the same docker compose deployment and so can communicate directly via docker dns |
I am using basic authentication. You can use any website to generate the hash file.
From the kotlin side a request can be made using Unirest and basic auth as follows
1 | Unirest.post("https://nginx/v2/send") |
Captcha and registering a number
To register a number with signal the number needs to go through a captcha + sms verification process.
The http request to register a number with a captcha token is
1 | curl -X POST -H "Content-Type: application/json" -d '{"captcha":"captcha-token-here"}' 'http://localhost:8080/v1/register/<number>' |
When this request is made an SMS with a verification code is sent to <number>
- that verification code needs to subsequently be sent to the verify endpoint using the following request
1 | curl -X POST -H "Content-Type: application/json" 'http://localhost:8080/v1/register/<nunber>/verify/<code>' |
Seems easy. All that is required is getting a captcha token and filling that in the first curl request. The official docs say to get the captcha token you need to do
1 | Go to https://signalcaptchas.org/registration/generate.html |
What happened next was something that is quite common in software - the docs don’t work.
So how do you get the captcha token?
First, I stumbled upon the repo https://gitlab.com/signald/captcha-helper
The code uses vala
a programming language? I couldn’t quite get this working or grok how vala works
Next is when I stumbled upon a repo that actually worked. https://gitlab.com/h0h0h0/signalcaptchahelper
provides an Xcode Mac project that can be run locally, the captcha can be solved and the token is then printed out.
So I cloned the project - pressed play - solved the captcha that came up in the little GUI window and got my captcha token.
After that, I was able to make the two requests above and get my textnow number registered as a signal user in the container.
Sending messages
Once the number is registered and the server has been exposed - A github actions workflow can be set up that runs every morning on a scheduled cron timer. The workflow runs some kotlin code which makes a request to https://api.aawadia.dev/misc/v1/daily?type=motivation
or https://api.aawadia.dev/misc/v1/daily?type=joke
or https://api.aawadia.dev/misc/v1/daily?type=quote
and sends the parsed result as a signal message to my phone.
1 | name: Send reminder |