Encryption

End-to-end encrypted texts from your own software: what is and isn't encrypted

Your software encrypts each text with your phone's public key, so SMSTunnel can't read it. What is encrypted today, what isn't, and how Maximum privacy works.

By NARBOWEB SRL, the team behind SMSTunnel Published 8 min read

When your website, shop or script sends a text through SMSTunnel, the text travels from your server to your SMSTunnel account and from there to your own Android phone, which sends it from your number. End-to-end encryption closes that path to everyone in the middle, including us: your software locks the text with your phone’s public key before it leaves your server, and only your phone can unlock it.

It’s optional, and it’s included on every plan, even Free. This article explains how it works, which integrations support it, how to turn it on, and, just as important, what it does not cover. Encryption you can’t describe exactly is worth little, so the limits come first-class here, not in a footnote.

How it works

There are three steps, and only the first and the last can read the text:

  1. Your software encrypts. It locks the text with your phone’s public key, on your own server, before anything leaves it. Each phone has its own RSA-2048 key pair; the private key never leaves the phone, and SMSTunnel stores only the public key.
  2. SMSTunnel carries. Our servers pass the locked text on, and erase it once it’s sent. We can’t read the message.
  3. Your phone sends. Only your phone holds the private key. It unlocks the text and sends it as a regular SMS from your number.

What SMSTunnel still sees with encryption on: your account, your phone and the time of each text, plus the number the text goes to, unless you use Maximum privacy (below).

What is encrypted, and what isn’t

TextEnd-to-end encrypted?
Texts your software encrypts (the API, the Node.js SDK, the WordPress plugin with encryption on)Yes, the words. The number stays visible in your account so your Messages page can show it
The same texts with Maximum privacy (Professional and Enterprise)Yes, the words and the number
Texts you type in the dashboardNo. Protected in transit and kept readable in your account, so you can see your history
Texts your software sends without encryptionNo. Kept readable in your account history
Sign-in codes from the WordPress pluginNo. The plugin doesn’t encrypt them
Texts your phone receives, if you sync themNo. Protected in transit and stored readable, so the dashboard can show them
The SMS itself, between your phone and the person you textNo. After your phone sends it, it travels over the mobile network like any other SMS

All traffic to and from SMSTunnel uses encrypted connections (TLS 1.2 or higher), whether or not a text is end-to-end encrypted. The table is about who can read the words once they arrive.

On received texts: end-to-end encryption for them, readable only on your phone and in a desktop app that’s coming, is being built.

Turn it on in WordPress

The SMSTunnel plugin for WordPress encrypts its texts for your phone before they leave WordPress. It needs your phone’s public key once, which it gets with a QR code:

  1. In WordPress, go to SMSTunnel → Encryption Settings.
  2. Start the encryption pairing to show a QR code.
  3. Scan the QR code with the SMSTunnel app on your phone.
  4. Done: texts from your site are now end-to-end encrypted.

During the pairing the phone shares only its public key. Its private key never leaves the phone, so SMSTunnel stores only encrypted text that it cannot read. Keep in mind the one exception from the table: the plugin’s sign-in codes for WordPress logins are not encrypted.

Encrypt from your own code

With the Node.js SDK

The official SDK does the whole job in one call: it fetches your phone’s public key, encrypts the number and the words, and sends the result.

import { SmsTunnelNodeClient } from '@narcisbodea/smstunnel-sdk/node';

const client = new SmsTunnelNodeClient({ apiKey: process.env.SMSTUNNEL_API_KEY! });

// Encrypts { to, message } with the phone's public key, then sends it.
await client.encryptAndSendSms('+14155550123', 'Your order has shipped.', 'device-id');

The device-id is the phone that will send the text: client.getDevices() lists your phones with their ids. In the SDK’s NestJS module, smsTunnelService.encryptAndSendSms(to, message) exchanges keys with the phone the first time and encrypts from then on.

Encryption has to run on your server. Browsers can’t do it with the SDK, and your API key must never reach a browser anyway.

With the REST API, in any language

Without the SDK, the steps are the same:

  1. Get the phone’s public key with GET /api/v1/devices/{deviceId}/public-key.
  2. Encrypt the JSON {"to": "+14155550123", "message": "…"} with it, on your server.
  3. Send the result to POST /api/v1/sms/send as encryptedPayload (base64), with "encrypted": true, the deviceId and, as well, to, so your Messages page can show the number.
{
  "to": "+14155550123",
  "deviceId": "671fe0d9c3b2a1f0e9d8c7b6",
  "encrypted": true,
  "encryptedPayload": "BASE64_OF_THE_ENCRYPTED_JSON"
}

You don’t have to work out the encryption format yourself: when you add an app in your dashboard and choose to encrypt end to end, it gives you ready code for PHP, Node.js, Python and C#. The API reference has the details.

One practical difference: an encrypted text counts toward your plan’s daily limit and is refused past it, because it can’t carry the short “free by smstunnel.io” line that the Free plan adds to the extra texts of the day.

Notice when the phone’s key changes

If the SMSTunnel app is reinstalled, the phone makes a new key pair. GET /api/v1/devices/{deviceId}/e2e-status returns whether encryption is on, whether a public key is stored, its fingerprint (the first 16 hex characters of a SHA-256 hash) and when it was created. Store the fingerprint after the first pairing and compare it before you send:

const result = await client.verifyDeviceKey('device-id', storedFingerprint);
if (result.needsRePairing) {
  // The app was reinstalled and the key changed: pair again before sending.
}

Maximum privacy: hide the number too

End-to-end encryption hides the words. On the Professional and Enterprise plans, Maximum privacy hides the number as well, so we can’t see who a text is for.

It works with a private certificate that you make, protected by a password only you know. You put the certificate in your software and on your phone, the key never passes through our servers, and from then on your phone refuses any text that isn’t sealed with it, including texts written in the dashboard.

1. Make the certificate

In your dashboard: Apps → Add an app → Encrypt end to end → Maximum privacy, which also gives you ready code for PHP, Node.js, Python and C#. Or make it on your own computer, where nothing leaves the machine:

npx -p @narcisbodea/smstunnel-sdk smstunnel-make-certificate

It asks for a password of at least 12 characters and saves smstunnel-certificate.json.

2. Put it on your phone

In the SMSTunnel app (version 1.0.9 or newer), open Settings → Private certificate and paste the contents of the file. If you’d rather scan a QR code, the dashboard shows one under Apps → Maximum privacy → “I already have one”.

3. Send sealed texts

import { readFileSync } from 'node:fs';
import { SmsTunnelNodeClient, openCertificate } from '@narcisbodea/smstunnel-sdk/node';

const client = new SmsTunnelNodeClient({ apiKey: process.env.SMSTUNNEL_API_KEY! });
const certificate = openCertificate(
  readFileSync('smstunnel-certificate.json', 'utf8'),
  process.env.SMSTUNNEL_CERT_PASSWORD,
);

await client.sendSealedSms('+14155550123', 'Your order has shipped.', 'device-id', certificate);

Over the REST API, a sealed text is sent as encryptedPayload with "encrypted": true and the deviceId, and without to.

What to know before you switch it on:

  • We still see your account, your phone and the time of each text.
  • If you sync received texts to your account, a reply shows the number it came from.
  • Your software needs the certificate and its password to send. Keep the password out of your code, for example in an environment variable.
  • We can’t recover a lost certificate or password. If you lose either, make a new certificate and put it on your phone and in your software again.
  • On the other plans, a text without to, or one sealed with a certificate, is refused with status 403.

How this compares

Among the tools that send texts from your own Android phone, httpSMS also offers optional end-to-end encryption: you set one key in its app and use the same key in your code, for sent and received texts. For textbee, we found no end-to-end encryption in its documentation, which says its API traffic uses HTTPS and that it stores your messages on its servers. SMSTunnel uses a key pair per phone, so the key that unlocks your texts never leaves the phone, and adds Maximum privacy for the number on Professional and Enterprise.

Common questions

Can SMSTunnel read my encrypted texts?

No. We only ever have the phone’s public key, which can lock a text but not unlock it, and we erase the locked text once it’s sent.

Is the SMS encrypted on its way to the person I text?

No. End-to-end here means from your software to your phone. Once your phone sends the text, it is a regular SMS on the mobile network.

Does encryption cost extra?

No. It’s on every plan, including Free. Maximum privacy comes with Professional ($39 a month) and Enterprise ($99 a month); see the pricing page.

Can apps with many clients use it?

Yes. On Enterprise, each client links their own phone, and your app can encrypt each text for the phone that will send it.

See also

More from the blog

Try it with your own phone

Install the Android app, sign in, and your own software can send its first text in a few minutes. The Free plan needs no credit card.