+## Table Of Contents
+
+
+
+
+- [Why?](#why)
+- [Web UI](#web-ui)
+- [API](#api)
+- [Android App](#android-app)
+- [Chat/forum](#chatforum)
+- [Features](#features)
+ - [End-to-end Encryption](#end-to-end-encryption)
+ - [Webhook](#webhook)
+ - [Back Pressure](#back-pressure)
+ - [Message Expiration](#message-expiration)
+- [API Clients](#api-clients)
+- [Flows](#flows)
+ - [Sending an SMS Message](#sending-an-sms-message)
+- [Self Host Setup - Docker](#self-host-setup---docker)
+ - [1. Setup Firebase](#1-setup-firebase)
+ - [2. Setup SMTP Email service](#2-setup-smtp-email-service)
+ - [3. Setup Cloudflare Turnstile](#3-setup-cloudflare-turnstile)
+ - [4. Download the code](#4-download-the-code)
+ - [5. Setup the environment variables](#5-setup-the-environment-variables)
+ - [6. Build and Run](#6-build-and-run)
+ - [7. Create the System User](#7-create-the-system-user)
+ - [8. Build the Android App.](#8-build-the-android-app)
+- [Integration Testing](#integration-testing)
+- [License](#license)
+
+
+
## Why?
I'm originally from Cameroon and I wanted an automated way to send and receive SMS messages using an API.
Unfortunately many countries don't support the ability to buy virtual phone numbers, and I could not find a good ready-made
solution that could help me send/receive SMS messages using a mobile phone using an intuitive http API.
-## Technology
-
-### Web
+## Web UI
The web interface https://httpsms.com is built using [Nuxt](https://nuxtjs.org/) and [Vuetify](https://vuetifyjs.com/en/).
It is hosted as a single page application on firebase. The source code is in the [web](./web) directory
-### API
+## API
The API https://api.httpsms.com is built using [Fiber](https://gofiber.io/), Go and [CockroachDB](https://www.cockroachlabs.com/) for the database.
-It rus as a serverless application on Google Cloud Run. The API documentation can be found here https://api.httpsms.com/index.html
+It runs as a serverless application on Google Cloud Run. The API documentation can be found here https://api.httpsms.com/index.html
```go
// Sending an SMS Message using Go
@@ -41,14 +77,34 @@ client.Messages.Send(context.Background(), &httpsms.MessageSendParams{
})
```
-### Android App
+## Android App
+
+[The Android App](https://apk.httpsms.com/HttpSms.apk) is a native application built using Kotlin with material design principles.
+This app must be installed on an Android phone before you can start sending and receiving SMS messages.
+
+[
](https://github.com/NdoleStudio/httpsms/releases/)
-[The Android App](https://github.com/NdoleStudio/httpsms/releases/download/v0.0.1/HttpSms.apk) is a native application
-built using Kotlin with material design principles. This app must be installed on an android phone before you can start
-sending and receiving SMS messages.
+## Chat/forum
+
+There are a few ways to get in touch with me and/or the rest of the community. Feel free to use any of these methods. Whatever
+works best for you:
+
+- [Discord server](https://discord.gg/kGk8HVqeEZ) - direct chat with the community
+- [GitHub issues](https://github.com/NdoleStudio/httpsms/issues) - questions, features, bugs
## Features
+### End-to-end Encryption
+
+You can encrypt your messages end-to-end using the military grade [AES-256 encryption](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
+algorithm. Your encryption key is stored only on our mobile phone so the even the server won't have any way to view the
+content of your SMS messages which are sent and received on your Android phone.
+
+### Webhook
+
+If you want to build advanced integrations, we support webhooks. The httpSMS platform can forward SMS messages received
+on the android phone to your server using a callback URL which you provide.
+
### Back Pressure
In-order not to abuse the SMS API on android, you can set a rate limit e.g 3 messages per minute. Such that even if you
@@ -62,7 +118,8 @@ will be notified.
## API Clients
-- Go: https://github.com/NdoleStudio/httpsms-go
+- [x] Go: https://github.com/NdoleStudio/httpsms-go
+- [x] JavaScript/TypeScript: https://github.com/NdoleStudio/httpsms-node
## Flows
@@ -70,17 +127,157 @@ will be notified.
```mermaid
sequenceDiagram
-User->>+Http Sms API: Call /v1/messages/send API
-Http Sms API-->>+Google Cloud Task: Schedule notification about new message
-Http Sms API-->>-User: Respond with 202 (Accepted)
-Google Cloud Task-->>+Http Sms API: [Async] Send notfication request
-Http Sms API-->>-Android App: Send push notification about new message
-Android App-->>Http Sms API: [Async] Fetch message
-Android App-->>Android App: Send Message using Andoid SMS API
-Android App-->>Http Sms API: [Async] Send result of sending SMS
-Android App-->>Http Sms API: [Async] Send Delivery Report
+User->>+httpSMS API: Call /v1/messages/send API
+httpSMS API-->>+Push Queue: Schedule notification about new message
+httpSMS API-->>-User: Respond with 202 (Accepted)
+Push Queue-->>+httpSMS API: [Async] Send notification request
+httpSMS API-->>-Android App: Send push notification about new message
+Android App-->>httpSMS API: [Async] Fetch message
+Android App-->>Android App: Send Message using Android SMS API
+Android App-->>httpSMS API: [Async] Send result of sending SMS
+Android App-->>httpSMS API: [Async] Send Delivery Report
+```
+
+## Self Host Setup - Docker
+
+### 1. Setup Firebase
+
+- The httpSMS application uses [firebase cloud messaging](https://firebase.google.com/docs/cloud-messaging) for sending push notifications to your Android phone to trigger an SMS message to be sent out.
+ Visit the [firebase console](https://console.firebase.google.com/) and create a new project and follow the [steps here](https://firebase.google.com/docs/web/setup#register-app) to get your firebase web SDK config credentials.
+ For example, I created a firebase project called `httpsms-docker` and this is my web SDK configuration
+
+```js
+const firebaseConfig = {
+ apiKey: "AIzaSyAKqPvj51igvvNNcRtxxxxx",
+ authDomain: "httpsms-docker.firebaseapp.com",
+ projectId: "httpsms-docker",
+ storageBucket: "httpsms-docker.appspot.com",
+ messagingSenderId: "668063041624",
+ appId: "1:668063041624:web:29b9e3b702796xxxx",
+ measurementId: "G-18VRYL2xxxx",
+};
+```
+
+- Enable `Email/Password` sign-in in the [Firebase console](https://console.firebase.google.com/u/0/), open the **Authentication** section. On the Sign in method tab, enable the `Email/password` sign-in method and click `Save`.
+ - The firebase `email/password` sign-in method has [a bug](https://github.com/firebase/firebaseui-web/issues/1040) which prevents you from signing in. The work around right now is to [disable email enumeration protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection#disable) on the firebase console.
+- Generate your firebase service account credentials by following the [steps here](https://firebase.google.com/docs/admin/setup#initialize_the_sdk_in_non-google_environments) and save the credentials in a file called `firebase-credentials.json` we will use this file to authenticate with the firebase admin SDK.
+- Generate your Android `google-services.json` file using [the instructions here](https://support.google.com/firebase/answer/7015592?hl=en#android&zippy=%2Cin-this-article) we will use it letter to configure the android app.
+
+### 2. Setup SMTP Email service
+
+The httpSMS application uses [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) to send emails to users e.g. when your Android phone has been offline for a long period of time.
+You can use a service like [mailtrap](https://mailtrap.io/) to create an SMTP server for development purposes.
+
+### 3. Setup Cloudflare Turnstile
+
+The message search route (`/v1/messages/search`) is protected by a [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/get-started/) captcha to prevent abuse. You need to set up a Turnstile widget for the search messages feature to work.
+
+1. Go to the [Cloudflare dashboard](https://dash.cloudflare.com/) and navigate to **Turnstile**.
+2. Add a new site and configure it for your self-hosted domain (e.g., `localhost` for local development).
+3. Note down the **Site Key** and **Secret Key** — you will need them for the frontend and backend environment variables respectively.
+
+### 4. Download the code
+
+Clone the httpSMS GitHub repository
+
+```bash
+git clone https://github.com/NdoleStudio/httpsms.git
+```
+
+### 5. Setup the environment variables
+
+- Copy the `.env.docker` file in the `web` directory into `.env`
+
+```bash
+cp web/.env.docker web/.env
+```
+
+- Update the environment variables in the `.env` file in the `web` directory with your firebase web SDK configuration in step 1 above
+
+```dotenv
+FIREBASE_API_KEY=
+FIREBASE_AUTH_DOMAIN=
+FIREBASE_PROJECT_ID=
+FIREBASE_STORAGE_BUCKET=
+FIREBASE_MESSAGING_SENDER_ID=
+FIREBASE_APP_ID=
+FIREBASE_MEASUREMENT_ID=
+
+# Cloudflare Turnstile site key from step 3
+CLOUDFLARE_TURNSTILE_SITE_KEY=
+```
+
+- Copy the `.env.docker` file in the `api` directory into `.env`
+
+```bash
+cp api/.env.docker api/.env
+```
+
+- Update the environment variables in the `.env` file in the `api` directory with your firebase service account credentials, SMTP server details, and Cloudflare Turnstile secret key.
+
+```dotenv
+# SMTP email server settings
+SMTP_USERNAME=
+SMTP_PASSWORD=
+SMTP_HOST=
+SMTP_PORT=
+
+# Firebase service account credentials
+FIREBASE_CREDENTIALS=
+
+# This is the `projectId` from your firebase web config
+GCP_PROJECT_ID=
+
+# Cloudflare Turnstile secret key from step 3
+CLOUDFLARE_TURNSTILE_SECRET_KEY=
+```
+
+- Don't bother about the `EVENTS_QUEUE_USER_API_KEY` and `EVENTS_QUEUE_USER_ID` settings. We will set that up later.
+
+### 6. Build and Run
+
+- Build and run the API, the web UI, database and cache using the `docker-compose.yml` file. It takes a while for build and download all the docker images.
+ When it's finished, you'll be able to access the web UI at http://localhost:3000 and the API at http://localhost:8000
+
+```bash
+docker compose up --build
+```
+
+### 7. Create the System User
+
+- The application uses the concept of a system user to process events asynchronously. You should manually create this user in `users` table in your database. Make sure you use the same `id` and `api_key` as the `EVENTS_QUEUE_USER_ID`, and `EVENTS_QUEUE_USER_API_KEY` in your `.env` file.
+
+ ```SQL
+ INSERT INTO users (id, api_key, email ) VALUES ('your-system-user-id', 'your-system-api-key', 'system@domain.com');
+ ```
+
+> [!IMPORTANT]
+> Restart your API docker container after modifying `EVENTS_QUEUE_USER_ID`, and `EVENTS_QUEUE_USER_API_KEY` in your `.env` file so that the httpSMS API can pick up the changes.
+
+### 8. Build the Android App.
+
+- Before building the Android app in [Android Studio](https://developer.android.com/studio), you need to replace the `google-services.json` file in the `android/app` directory with the file which you got from step 1. You need to do this for the firebase FCM messages to work properly.
+
+## Integration Testing
+
+The project includes end-to-end integration tests that validate the complete SMS send/receive lifecycle. Tests run the full stack (API, PostgreSQL, Redis) in Docker alongside a phone emulator that simulates an Android device.
+
+📖 **Full documentation:** [`tests/README.md`](tests/README.md)
+
+**Quick run:**
+
+```bash
+cd tests
+bash generate-firebase-credentials.sh
+export FIREBASE_CREDENTIALS=$(jq -c . firebase-credentials.json)
+docker compose up -d --build --wait
+docker compose wait seed && sleep 2
+go test -v -timeout 120s ./...
+docker compose down -v
```
+Integration tests also run automatically in CI on every push/PR to `main`.
+
## License
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
+This project is licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - see the [LICENSE](LICENSE) file for details
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..35c692f8c
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,13 @@
+# Security Policy
+
+## Supported Versions
+
+As of today, I only support the latest version of httpSMS in the `main` branch of github. Please make sure you stay up-to-date.
+
+| Version | Supported |
+| ------- | ------------------ |
+| main | :white_check_mark: |
+
+## Reporting a Vulnerability
+
+Please report severe security issues privately via arnold@httpsms.com or by sending me a private message on [Discord](https://discord.gg/kGk8HVqeEZ).
diff --git a/android/.gitignore b/android/.gitignore
index aa724b770..6c13541ca 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -12,4 +12,5 @@
/captures
.externalNativeBuild
.cxx
+.kotlin/sessions/
local.properties
diff --git a/android/app/build.gradle b/android/app/build.gradle
deleted file mode 100644
index d1f826827..000000000
--- a/android/app/build.gradle
+++ /dev/null
@@ -1,71 +0,0 @@
-plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
- id 'com.google.gms.google-services'
- id "io.sentry.android.gradle" version "3.1.2"
-}
-
-def getGitHash = { ->
- def stdout = new ByteArrayOutputStream()
- exec {
- commandLine 'git', 'rev-parse', '--short', 'HEAD'
- standardOutput = stdout
- }
- return stdout.toString().trim()
-}
-
-android {
- compileSdk 33
-
- buildToolsVersion "32.0.0"
-
- defaultConfig {
- applicationId "com.httpsms"
- minSdk 26
- targetSdk 33
- versionCode 1
- versionName "${getGitHash()}"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- debug {
- manifestPlaceholders["sentryEnvironment"] = "development"
- }
- release {
- manifestPlaceholders["sentryEnvironment"] = "production"
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
- namespace 'com.httpsms'
-}
-
-dependencies {
- implementation platform('com.google.firebase:firebase-bom:30.1.0')
- implementation 'com.google.firebase:firebase-analytics-ktx'
- implementation 'com.google.firebase:firebase-messaging-ktx'
- implementation 'com.squareup.okhttp3:okhttp:4.10.0'
- implementation 'com.jakewharton.timber:timber:5.0.1'
- implementation 'androidx.preference:preference:1.2.0'
- implementation 'androidx.work:work-runtime-ktx:2.7.1'
- implementation 'androidx.core:core-ktx:1.9.0'
- implementation "androidx.cardview:cardview:1.0.0"
- implementation 'com.beust:klaxon:5.5'
- implementation 'androidx.work:work-runtime-ktx:2.7.1'
- implementation 'androidx.appcompat:appcompat:1.4.2'
- implementation 'org.apache.commons:commons-text:1.9'
- implementation 'com.google.android.material:material:1.6.1'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-}
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
new file mode 100644
index 000000000..f670b8bb7
--- /dev/null
+++ b/android/app/build.gradle.kts
@@ -0,0 +1,79 @@
+plugins {
+ id("com.android.application")
+ id("com.google.gms.google-services")
+ id("io.sentry.android.gradle") version "6.14.0"
+ id("org.jetbrains.kotlin.plugin.compose")
+}
+
+val gitHash = providers.exec {
+ commandLine("git", "rev-parse", "--short", "HEAD")
+}.standardOutput.asText.map { it.trim() }
+
+android {
+ compileSdk = 37
+
+ defaultConfig {
+ applicationId = "com.httpsms"
+ minSdk = 28
+ targetSdk = 37
+ versionCode = 1
+ versionName = gitHash.getOrElse("unknown")
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ getByName("debug") {
+ manifestPlaceholders["sentryEnvironment"] = "development"
+ }
+ getByName("release") {
+ manifestPlaceholders["sentryEnvironment"] = "production"
+ isMinifyEnabled = false
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ namespace = "com.httpsms"
+
+ buildFeatures {
+ buildConfig = true
+ compose = true
+ }
+}
+
+dependencies {
+ val composeBom = platform("androidx.compose:compose-bom:2026.06.01")
+ implementation(composeBom)
+ androidTestImplementation(composeBom)
+
+ implementation("androidx.compose.ui:ui")
+ implementation("androidx.compose.ui:ui-graphics")
+ implementation("androidx.compose.ui:ui-tooling-preview")
+ implementation("androidx.compose.material3:material3")
+ implementation("androidx.compose.material:material-icons-extended")
+ implementation("androidx.activity:activity-compose:1.13.0")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0")
+
+ implementation(platform("com.google.firebase:firebase-bom:34.16.0"))
+ implementation("com.journeyapps:zxing-android-embedded:4.3.0")
+ implementation("com.google.firebase:firebase-analytics")
+ implementation("com.google.firebase:firebase-messaging")
+ implementation("com.squareup.okhttp3:okhttp:5.4.0")
+ implementation("com.jakewharton.timber:timber:5.0.1")
+ implementation("androidx.preference:preference-ktx:1.2.1")
+ implementation("androidx.work:work-runtime-ktx:2.11.2")
+ implementation("androidx.core:core-ktx:1.19.0")
+ implementation("androidx.cardview:cardview:1.0.0")
+ implementation("com.beust:klaxon:5.6")
+ implementation("androidx.appcompat:appcompat:1.7.1")
+ implementation("org.apache.commons:commons-text:1.15.0")
+ implementation("com.google.android.material:material:1.14.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.2.1")
+ implementation("com.googlecode.libphonenumber:libphonenumber:9.0.34")
+ implementation("com.klinkerapps:android-smsmms:5.2.6")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.3.0")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 237e8ca9e..86ca0a514 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -3,17 +3,24 @@
xmlns:tools="http://schemas.android.com/tools">
- ` + content + ``) + return formattedPayload, richPayload +} + +func indentEventPayloadJSON(payload string) (string, bool) { + var formatted bytes.Buffer + if err := json.Indent(&formatted, []byte(payload), "", " "); err != nil { + return payload, false + } + + return formatted.String(), true +} + +func highlightEventPayloadJSON(payload string) string { + var highlighted strings.Builder + highlighted.Grow(len(payload)) + + for index := 0; index < len(payload); { + switch { + case payload[index] == '"': + end := eventPayloadJSONStringEnd(payload, index) + style := jsonStringStyle + if eventPayloadNextNonSpace(payload, end) == ':' { + style = jsonKeyStyle + } + writeEventPayloadToken(&highlighted, style, payload[index:end]) + index = end + case payload[index] == '-' || isEventPayloadDigit(payload[index]): + end := index + 1 + // json.Indent already validated this as JSON, so this continuation set only sees JSON number bytes. + for end < len(payload) && isEventPayloadNumberCharacter(payload[end]) { + end++ + } + writeEventPayloadToken(&highlighted, jsonNumberStyle, payload[index:end]) + index = end + case strings.HasPrefix(payload[index:], "true"): + writeEventPayloadToken(&highlighted, jsonLiteralStyle, "true") + index += len("true") + case strings.HasPrefix(payload[index:], "false"): + writeEventPayloadToken(&highlighted, jsonLiteralStyle, "false") + index += len("false") + case strings.HasPrefix(payload[index:], "null"): + writeEventPayloadToken(&highlighted, jsonLiteralStyle, "null") + index += len("null") + default: + highlighted.WriteString(html.EscapeString(payload[index : index+1])) + index++ + } + } + + return highlighted.String() +} + +func eventPayloadJSONStringEnd(payload string, start int) int { + escaped := false + for index := start + 1; index < len(payload); index++ { + switch { + case escaped: + escaped = false + case payload[index] == '\\': + escaped = true + case payload[index] == '"': + return index + 1 + } + } + + return len(payload) +} + +func eventPayloadNextNonSpace(payload string, start int) byte { + for index := start; index < len(payload); index++ { + switch payload[index] { + case ' ', '\n', '\r', '\t': + continue + default: + return payload[index] + } + } + + return 0 +} + +func isEventPayloadDigit(value byte) bool { + return value >= '0' && value <= '9' +} + +func isEventPayloadNumberCharacter(value byte) bool { + return isEventPayloadDigit(value) || + value == '-' || + value == '+' || + value == '.' || + value == 'e' || + value == 'E' +} + +func writeEventPayloadToken(builder *strings.Builder, style string, token string) { + builder.WriteString(``) + builder.WriteString(html.EscapeString(token)) + builder.WriteString(``) +} diff --git a/api/pkg/emails/event_payload_formatter_test.go b/api/pkg/emails/event_payload_formatter_test.go new file mode 100644 index 000000000..9617ebb0e --- /dev/null +++ b/api/pkg/emails/event_payload_formatter_test.go @@ -0,0 +1,113 @@ +package emails + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormatEventPayloadIndentsAndHighlightsJSON(t *testing.T) { + payload := `{"message":"hello","count":2,"ratio":1.5,"enabled":true,"disabled":false,"missing":null,"nested":{"value":"ok"}}` + + plain, rich := formatEventPayload(payload) + html := string(rich) + + assert.Equal(t, `{ + "message": "hello", + "count": 2, + "ratio": 1.5, + "enabled": true, + "disabled": false, + "missing": null, + "nested": { + "value": "ok" + } +}`, plain) + assert.Contains(t, html, `"message"`) + assert.Contains(t, html, `"hello"`) + assert.Contains(t, html, `2`) + assert.Contains(t, html, `1.5`) + assert.Contains(t, html, `true`) + assert.Contains(t, html, `false`) + assert.Contains(t, html, `null`) + assert.Contains(t, html, `white-space:pre-wrap`) +} + +func TestFormatEventPayloadEscapesPayloadHTML(t *testing.T) { + plain, rich := formatEventPayload(`{"message":"&"}`) + html := string(rich) + + assert.Contains(t, plain, ` + + +
+ httpSMS is an + open source + application that converts your android phone into an SMS gateway so you + can send and receive SMS messages using a simple HTTP API. +
++ Enter your email address to reset your password +
++ By continuing, you are indicating that you accept our + + Terms of Service + + and + + Privacy Policy. +
++ Start sending messages +
++ Install the mobile app on your Android phone to start sending messages. + You can also + message us on Discord + to help set things up. +
++ {{ phoneCountry(phonesStore.owner) }} +
+{{ error.message }}
+
+
+
+ + Your next bill is for ${{ plan.price }} on + {{ + new Date( + authStore.user.subscription_renews_at!, + ).toLocaleDateString() + }} +
++ You are on the life time plan which costs + ${{ plan.price }} +
++ You will be downgraded to the FREE plan on + {{ + new Date( + authStore.user.subscription_ends_at!, + ).toLocaleDateString() + }} +
++ {{ formatDecimal(totalMessages) }}/{{ + formatDecimal(plan.messagesPerMonth) + }} + messages +
++ You will be downgraded to the free plan at the end + of the current billing period on + {{ + new Date( + authStore.user.subscription_renews_at!, + ).toLocaleDateString() + }} +
++ Send and receive 5,000 to 20,000 messages per month +
++ Send and receive 50,000 to 200,000 messages per + month +
+
+ This is the summary of the sent messages and received messages
+ from
+
Messages Sent
+Messages Received
++ This is a list of your last 10 subscription payments made using + our payment provider + + Lemon Squeezy. +
++ Summary of all the sent and received messages in the past 12 + billing periods +
++ We have added support for end-to-end encryption for SMS messages so + that no one can see the content of the messages you send using httpSMS + except you. +
++ You setup an encryption key which you use to encrypt your messages + before making an API request to httpSMS and you also use the same key + to decrypt the messages you receive from httpSMS via our + webhook events. We are using the + AES 256 + encryption algorithm to encrypt and decrypt the messages. +
+ ++ ⬇️ Download and install + the httpSMS Android app on your phone and set you encryption key under + the App Settings page of the app. +
++ We use the AES-256 encryption algorithm to encrypt the SMS messages. + This algorithm requires a an encryption key which is 256 bits to work + around this, we will hash any encryption key you set on the mobile app + using the sha-256 algorithm so that it will always produce a key which + is 256 bits. +
++ The AES algorithm also has an initialization vector (IV) parameter + which is used to ensure that the same value encrypted multiple times + will not produce the same encrypted value. The IV is 16 bits and it is + appended to the encrypted message before encoding it in base64. +
++ When you use our client libraries it will automatically take care of + encrypting your message so you don't have to deal with creating the + initialization vector and encoding the payload yourself. +
+ +import HttpSms from "httpsms"
+
+const client = new HttpSms("" /* API Key from https://httpsms.com/settings */);
+
+const key = "Password123";
+
+const encryptedMessage = client.cipher.encrypt(key, "This is a sample text message");
+
+// The encrypted message looks like this, note that you will get a different encrypted message when you run this code on your computer
+// Qk3XGN5+Ax38Ig01m4AqaP6Y0b0wYpCXtx59sU23uVLWUU/c7axF7LozDg==
+ import "github.com/NdoleStudio/httpsms-go"
+
+client := htpsms.New(htpsms.WithAPIKey(""/* API Key from https://httpsms.com/settings */))
+
+key := "Password123" // use the same key on the Android app
+encryptedMessage := client.Cipher.Encrypt(key, "This is a test text message")
+
+// The encrypted message looks like this, note that you will get a different encrypted message when you run this code on your computer
+// Qk3XGN5+Ax38Ig01m4AqaP6Y0b0wYpCXtx59sU23uVLWUU/c7axF7LozDg==
+
+ After generating the encrypted message payload, you can send it
+ directly using the httpSMS API. Make sure to set
+ encrypted: true in the JSON request payload so that
+ httpSMS knows that the message is encrypted and it will be decoded in
+ the Android app before sending to your recipient.
+
import HttpSms from "httpsms"
+
+client.messages.postSend({
+ content: encryptedMessage,
+ from: '+18005550199',
+ encrypted: true,
+ to: '+18005550100',
+})
+.then((message) => {
+ console.log(message.id); // log the ID of the sent message
+});
+ import "github.com/NdoleStudio/httpsms-go"
+
+client.Messages.Send(context.Background(), &httpsms.MessageSendParams{
+ Content: encryptedMessage,
+ From: "+18005550199",
+ To: "+18005550100",
+ Encrypted: true,
+})
+ + When you make the API request, the message will be decrypted before + sending to the recipient. This is a screenshot of the SMS message + which is sent to the recipient. +
+
+ When your android phone receives a new message, it will be encrypted
+ with the encryption Key on your Android phone before it is delivered
+ to your server's webhook endpoint. You can configure webhooks by
+ following
+
import HttpSms from "httpsms"
+
+const client = new HttpSms("" /* API Key from https://httpsms.com/settings */);
+
+// The payload in the webhook HTTP request looks like this
+/*
+{
+ "specversion": "1.0",
+ "id": "8dca3b0a-446a-4a5d-8d2a-95314926c4ed",
+ "source": "/v1/messages/receive",
+ "type": "message.phone.received",
+ "datacontenttype": "application/json",
+ "time": "2024-01-21T12:27:29.1605708Z",
+ "data": {
+ "message_id": "0681b838-4157-44bb-a4ea-721e40ee7ca7",
+ "user_id": "XtABz6zdeFMoBLoltz6SREDvRSh2",
+ "owner": "+37253920216",
+ "encrypted": true,
+ "contact": "+37253920216",
+ "timestamp": "2024-01-21T12:27:17.949Z",
+ "content": "bdmZ7n6JVf/ST+SoNlSaOGUL1DcL5705ETw8GAB4llYBgE9HOOL+Pu/h+w==",
+ "sim": "SIM1"
+ }
+}
+*/
+
+const encryptedMessage = "bdmZ7n6JVf/ST+SoNlSaOGUL1DcL5705ETw8GAB4llYBgE9HOOL+Pu/h+w==" // get the encrypted message from the request payload
+const encryptionkey = "Password123" // use the same key on the Android app
+const decryptedMessage = client.cipher.decrypt(encryptionkey, encryptedMessage)
+
+// This is a test text message
+ import "github.com/NdoleStudio/httpsms-go"
+
+client := htpsms.New(htpsms.WithAPIKey(/* API Key from https://httpsms.com/settings */))
+
+// The payload in the webhook HTTP request looks like this
+/*
+{
+ "specversion": "1.0",
+ "id": "8dca3b0a-446a-4a5d-8d2a-95314926c4ed",
+ "source": "/v1/messages/receive",
+ "type": "message.phone.received",
+ "datacontenttype": "application/json",
+ "time": "2024-01-21T12:27:29.1605708Z",
+ "data": {
+ "message_id": "0681b838-4157-44bb-a4ea-721e40ee7ca7",
+ "user_id": "XtABz6zdeFMoBLoltz6SREDvRSh2",
+ "owner": "+37253920216",
+ "encrypted": true,
+ "contact": "+37253920216",
+ "timestamp": "2024-01-21T12:27:17.949Z",
+ "content": "bdmZ7n6JVf/ST+SoNlSaOGUL1DcL5705ETw8GAB4llYBgE9HOOL+Pu/h+w==",
+ "sim": "SIM1"
+ }
+}
+*/
+
+encryptedMessage = "bdmZ7n6JVf/ST+SoNlSaOGUL1DcL5705ETw8GAB4llYBgE9HOOL+Pu/h+w==" // get the encrypted message from the request payload
+encryptionkey := "Password123" // use the same key on the Android app
+decryptedMessage := client.Cipher.Decrypt(encryptionkey, encryptedMessage)
+
+// This is a test text message
+ + Congratulations, you have successfully configured your Android phone + to send and receive SMS messages with end-to-end encryption. Don't + hesitate to contact us if you face any problems while following this + guide. +
+ +
+ You can now program your android phone to forward messages received on
+ your phone to your server and trigger powerful automations with tools
+ like Zapier and IFTTT. I created an open source application called
+
+ Create an account on the httpSMS web application and copy your API key
+ from the settings page.
+
+ ⬇️ Download and install + the httpSMS android app on your phone and sign in using your API KEY + which you copied above. This app listens for SMS messages received on + your android phone. +
+
+ Once the application has been installed, it will be listening for SMS
+ messages received on the android phone. You can configure the
+ application to sent this SMS message to your server URL using a
+ webhook. You can configure this URL under the settings page in the
+ httpSMS application
+
+ Congratulations, you have successfully set up SMS forwarding from your + Android phone to a webhook! This powerful automation tool can help you + streamline your business workflow and save you time and effort. +
++ You can also trigger the httpSMS application to send an SMS a simple + API. You can find more information on the documentation page at + https://docs.httpsms.com +
+Until the next time✌️
+ +
+ In Android 15 (Vanilla Ice Cream), the
+ android.permission.SEND_SMS and
+ android.permission.RECEIVE_SMS permissions are now hard
+ restricted and cannot be granted
+ via the runtime permissions interface.
+
SEND_SMS and
+ RECEIVE_SMS permissions will allow an Android app to be
+ able to read and send SMS messages on your phone. Make sure you trust
+ the application before allowing these permissions.
+ + Long press the icon of the android app which you want to grant the + permission and select "App info" +
+
+ On the App Info page, click on the menu button
+
+ Once you have allowed the restricted settings from step 2 above, You + can navigate to Permissions ➡️ SMS and tap the Allow button to + grant SMS permissions to the android app. +
++ Congratulations, you have successfully configured SMS permissions on + your Android app. Don't hesitate to contact us if you face any + problems while following this guide. +
+ ++ Send personalized SMS messages to multiple phone numbers for less than + $0.002 per SMS message. You can also configure every SMS + message in your Excel spreadsheet so they are unique for each + recipient phone number. +
+ +
+ Create an account on
+
+ ⬇️ Download and install + the httpSMS android app on your phone and sign in using your API KEY + which you copied above. This app listens for SMS messages received on + your android phone. +
+
+ Download the
+ httpSMS Excel file template
+ and edit it with your favorite spreadsheet software e.g Excel, Google
+ Sheets, Open Office etc. Fill in the phone number which you registered
+ in httpSMS in the FromPhoneNumber column and fill in the
+ number of the recipient of the SMS in the
+ ToPhoneNumber column. Also add the SMS which you want to
+ send in the message in the Content column.
+
FromPhoneNumber from step 2
+ above in your Excel file
+
+ Visit the
+
+ Don't hesitate to + contact us + if you face any issues sending bulk SMS messages from your Excel files + by following this tutorial. +
+Until the next time✌️
+ ++ {{ article.description }} +
++ Send personalized SMS messages to your users in bulk using your + Android phone. The good news is, you don't have to write a single + piece of code, just upload your CSV file and we will take care of the + rest. +
+ +
+ CSV is an abbreviation for comma-separated values. A CSV file allows
+ data to be saved in a table structured format using a comma
+ , to separate the various cells of a table and a new line
+ to separate the various rows in the table. CSV files can be used with
+ any spreadsheet program, such as Microsoft Excel, Open Office Calc, or
+ Google Sheets.
+
+ Create an account on
+
+ ⬇️ Download and install + the httpSMS android app on your phone and sign in using your API KEY + which you copied above. This app listens for SMS messages received on + your android phone. +
+
+ Download the
+ httpSMS CSV file template
+ and edit it with your favorite spreadsheet software e.g Excel, Google
+ Sheets or even a text editor like notepad. Fill in the phone number
+ which you registered in httpSMS in the
+ FromPhoneNumber column and fill in the number of the
+ recipient of the SMS in the ToPhoneNumber column. Also
+ add the SMS which you want to send in the message in the
+ Content column.
+
FromPhoneNumber from step 2
+ above in your CSV file
+
+ Visit the
+
+ Don't hesitate to + contact us + if you face any issues sending bulk SMS messages from your CSV files + by following this tutorial. +
+Until the next time✌️
+ ++ In an era dominated by social media, instant messaging apps, and + ever-evolving communication technologies, it's easy to overlook the + humble yet remarkably resilient Short Message Service (SMS). Since its + inception in the 1990s, SMS has stood the test of time, remaining one + of the most widely used and reliable means of mobile communication. +
++ Whether you're a business owner looking to optimize your communication + strategy, a developer seeking to integrate SMS functionality into your + applications, or simply intrigued by the enduring charm of SMS, this + article will explain how to setup your Android phone to send SMS + messages. +
+ +
+ Create an account on
+
+ ⬇️ Download and install + the httpSMS android app on your phone and sign in using your API KEY + which you copied above. This app listens for SMS messages received on + your android phone. +
+
+ Now that you have setup your android phone correctly on httpSMS, you
+ can write the python code below in a new file named
+ send_sms.py. This code will send and SMS and after
+ running the script via your Android phone to the recipient phone
+ number specified in the payload.
+
api_key from step 1 and also
+ use the correct to and from phone numbers in
+ the payload variable.
+ import requests
+import json
+
+api_key = "" # Get API Key from https://httpsms.com/settings
+
+url = 'https://api.httpsms.com/v1/messages/send'
+
+headers = {
+ 'x-api-key': api_key,
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+}
+
+payload = {
+ "content": "This is a sample text message sent via python",
+ "from": "+18005550199", # This is the phone number of your android phone
+ "to": "+18005550100" # This is the recipient phone number
+}
+
+response = requests.post(url, headers=headers, data=json.dumps(payload))
+
+print(json.dumps(response.json(), indent=4))
+
+ Run the code above with the command
+ python send_sms.py and check the phone specified in the
+ to field of the payload to verify that the
+ message has been received successfully.
+
+ Congratulations, you have successfully configured your android phone + to send SMS messages via python. You can now reuse this code to send + SMS messages from your python applications. +
+
+ If you are also interested in forwarding incoming SMS from your
+ android phone to your server, checkout our
+
Until the next time✌️
+ ++ Automate sending personalized SMS messages each time a new row is + added to your Google Sheets document using Zapier. You don't need to + write any code to make this happen and you can personalize the SMS + messages which are sent out. +
+ ++ Create a new Zap on Zapier and select Google Sheets as the trigger. + The event name should be "New Spreadsheet Row" if you want to + send an SMS message every time a new row is added to your Google + Sheets document. +
++ On the Zap, select the Spreadsheet which you have on google + drive and make sure to select the correct Worksheet. +
++ An action is what happens after the trigger. In this case, we want to + send an SMS message to the customer who made the purchase. Select + Webhooks By Zapier as the action app and select + Custom Request as the action event. +
+
+ On the Action section in Zapier, set the Method to
+ Post. Set the URL to
+ https://api.httpsms.com/v1/messages/send. Set the
+ Data Pass-Through to false. In the Data
+ field, add the following JSON payload.
+
{
+ "content": "Hello [Name]\nThanks for ordering [Product] via our shopify store. Your order will be shipped today!",
+ "from": "+18005550199",
+ "to": "[ToPhoneNumber]"
+}
+ [Name] variable contains the name of the customer on the
+ spreadsheet. [Product] contains the name of the product
+ which was bought and [ToPhoneNumber] contains the phone
+ number of the customer who made the purchase. You can use your own
+ custom message with your own set of variables according to your
+ spreadsheet. Change the from field to the phone number
+ which you registered on httpsms.com.
+
+ On the headers section add a new header called
+ x-api-key and the value of this header should be your API
+ key on
+
+ Also add a new header called Content-Type and the value
+ of this header should be application/json
+
+ The final configuration of the action should look like the screenshot + below. +
++ Publish your zap and you will automatically trigger httpsms to send an + SMS to your customer when ever you add a new row in the google sheet. + Don't hesitate to + contact us + if you face any issues configuring your zap to send SMS messages from + your Google Sheets by following this tutorial. +
+Until the next time✌️
+ +
+ Fill in our bulk SMS
+ CSV template
+ or our
+ Excel template
+ and upload it here to send your SMS messages to multiple
+ recipients at once. You can also configure
+
+ Your 10 most recent bulk SMS uploads are shown below, including a + delivery status breakdown for each batch. Click on a row to see + individual messages on the search page. +
++ Use httpSMS as a lightweight CRM by adding your contacts here. Your + message threads will show contact names instead of phone numbers, + making conversations easier to recognize and manage. Add contacts + individually, or fill in our + CSV template + and upload it to import your contact list in bulk. +
+ ++ {{ + searchTerm + ? 'No contacts match your search' + : 'No contacts yet' + }} +
++ {{ + searchTerm + ? 'Try a different name, phone number or email.' + : 'Add your first contact or import them from a CSV file.' + }} +
+
+ Download the
+ CSV template, fill it in and upload it here. Separate multiple emails or phone
+ numbers within a cell using a semicolon (;).
+
We couldn't import your file:
++ Every 15 minutes, the httpSMS app on your Android phone sends a + heartbeat event to the httpsms API to show that it is alive. The + reason for this is because the Android operating system sometimes + kills an application to save battery + https://dontkillmyapp.com. +
++ If httpSMS doesn't get any heartbeat event in a 1-hour interval, + you will get an email notification about it so you can check if + there is an issue with your Android phone. +
++ The table below shows the last 100 heartbeat events received from + the httpSMS app on your Android phone. +
++ ⚡Trusted by 23,273+ users who send/receive more than + 500,000 messages per month. +
+
+
+ import HttpSms from 'httpsms'
+
+const client = new HttpSms('' /* Get the API Key from https://httpsms.com/settings */);
+
+client.messages.postSend({
+ content: 'This is a sample text message',
+ from: '+18005550199', // Put the correct phone number here
+ to: '+18005550100', // Put the correct phone number here
+})
+.then((message) => {
+ console.log(message.id); // log the ID of the sent message
+})
+ <?php
+$apiKey = "Get API Key from https://httpsms.com/settings";
+
+$options = array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'content' => json_encode( [
+ 'content' => 'This is a sample text message',
+ 'from' => "+18005550199",
+ 'to' => "+18005550100"
+ ]),
+ 'header'=> "Content-Type: application/json\r\n" .
+ "Accept: application/json\r\n" .
+ "x-api-key: $apiKey\r\n"
+ )
+);
+
+$context = stream_context_create( $options );
+$result = file_get_contents( "https://api.httpsms.com/v1/messages/send", false, $context );
+
+echo $result;
+ import requests
+import json
+
+api_key = "Get API Key from https://httpsms.com/settings"
+
+url = 'https://api.httpsms.com/v1/messages/send'
+
+headers = {
+ 'x-api-key': api_key,
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+}
+
+payload = {
+ "content": "This is a sample text message",
+ "from": "+18005550199",
+ "to": "+18005550100"
+}
+
+response = requests.post(url, headers=headers, data=json.dumps(payload))
+
+print(json.dumps(response.json(), indent=4))
+ import "github.com/NdoleStudio/httpsms-go"
+
+client := htpsms.New(htpsms.WithAPIKey(/* API Key from https://httpsms.com/settings */))
+
+client.Messages.Send(context.Background(), &httpsms.MessageSendParams{
+ Content: "This is a sample text message",
+ From: "+18005550199",
+ To: "+18005550100",
+})
+ var client = HttpClient.newHttpClient();
+var apiKey = "Get API Key from https://httpsms.com/settings";
+
+var payload = """
+ {
+ "content": "This is a sample text message",
+ "from": "+18005550199",
+ "to": "+18005550100"
+ }
+ """;
+
+var request = HttpRequest.newBuilder()
+ .uri(URI.create("https://api.httpsms.com/v1/messages/send"))
+ .header("accept", "application/json")
+ .header("Content-Type", "application/json")
+ .header("x-api-key", apiKey)
+ .POST(HttpRequest.BodyPublishers.ofString(payload))
+ .build();
+
+var response = client.send(request, HttpResponse.BodyHandlers.ofString());
+System.out.println(response.body());
+ curl --location --request POST 'https://api.httpsms.com/v1/messages/send' \
+--header 'x-api-key: Get API Key from https://httpsms.com/settings' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "from": "+18005550199",
+ "to": "+18005550100",
+ "content": "This is a sample text message"
+}'
+ var client = new HttpClient();
+client.DefaultRequestHeaders.Add("x-api-key", ""/* Get API Key from https://httpsms.com/settings */);
+
+var response = await client.PostAsync(
+ "https://api.httpsms.com/v1/messages/send",
+ new StringContent(
+ JsonSerializer.Serialize(new {
+ from = "+18005550199",
+ To = "+18005550100",
+ Content = "This is a sample text message",
+ }),
+ Encoding.UTF8,
+ "application/json"
+ )
+);
+
+Console.WriteLine(await response.Content.ReadAsStringAsync());
+ + Monthly +
+
+ Yearly
+
+ Try sending and receiving SMS on your hobby websites and + experiments. +
++ $0 +
++ No credit card required +
+
+
+
+
+
+ Send and receive more SMS messages like a pro with advanced + features. +
++ $10/month +
++ $100/year +
++ or $100 per year +
++ or $8.33 per month +
+
+
+
+
+
+ Send and receive up to {{ planMessages }} SMS messages like a + power user. +
++ ${{ planMonthlyPrice }}/month +
++ ${{ planYearlyPrice }}/year +
++ or ${{ planYearlyPrice }} per year +
++ or ${{ planYearlyMonthlyPrice }} per month +
+
+
+
+
+
Joysankar M.
++ httpSMS is free platform which transforms your phone into an sms + server! It has no hard limit also. It is an + innovative idea, I have not seen such tech before. If you + have an sms active pack in your phone then good to go + with httpSMS. +
+Edmund Ciego
++ "Outstanding product. Literally have been using this for + years since we don't have an sms gateways that can handle http + requests costing less than 50 cent per sms in my Country. + Love the product and the support! Great work Arnold!" +
++ If you still cannot find the answer to your question, + send us an email or ask in + our Discord channel. +
++ The httpSMS application works only on Android phones at the + moment since Apple doesn't allow you to install a custom SMS + messaging app. +
++ The httpSMS Android app works from Android 9 (Pie) and above. + So you can install the application on your old Android phone + which you don't use anymore. +
++ We do have packages that allow up to 200,000 SMS messages per + month but you can + send us an email if + you will like to send more messages so we create a custom plan + just for you. +
++ No you cannot. When you send an SMS message using the httpSMS + app it uses your SIM card to send the message so the recipient + will see your phone number as the sender of the SMS. You + cannot use your brand name as the sender ID. +
+
+ Join 23,273+ users who send/receive more than
+
+ 500,000 messages per month
+
+ Enter the recipient's phone number and your message below, and
+ we'll deliver a real SMS it through your connected Android phone.
+ You can also text a short code like
+
+ If you have multiple phones, you can create unique phone API keys
+ for your different Android phones. These API keys can only be used
+ on the specific mobile phone when it calls the httpSMS server for
+ specific actions like sending heartbeats, registering received
+ messages, delivery reports etc. If you want to interact with the
+ full
+ httpSMS API, use the API key under your account settings page instead
+
{{ formatPhoneNumber(activePhoneNumber) }} from your
+ phone API key. You will have to logout and login again on the
+ httpSMS Android app on the phone which is currently using this
+ API key.
+ + Ndole Studio built the httpSMS service as an open source project. This + SERVICE is provided by Ndole Studio and is intended for use as is. + This page is used to inform visitors regarding our policies with the + collection, use, and disclosure of Personal Information if anyone + decided to use our Service. If you choose to use our Service, then you + agree to the collection and use of information in relation to this + policy. The Personal Information that we collect is used for providing + and improving the Service. We will not use or share your information + with anyone except as described in this Privacy Policy. The terms used + in this Privacy Policy have the same meanings as in our Terms and + Conditions, which are accessible at httpSMS unless otherwise defined + in this Privacy Policy. +
+ ++ For a better experience, while using our Service, we may require you + to provide us with certain personally identifiable information, + including but not limited to email, full name. The information that we + request will be retained by us and used as described in this privacy + policy. +
++ httpSMS does use third-party services that may collect information + used to identify you. Link to the privacy policy of third-party + service providers used by httpSMS: +
++ We want to inform you that whenever you use our Service, in a case of + an error in the app we collect data and information (through + third-party products) on your phone and computer called Log Data. This + Log Data may include information such as your device Internet Protocol + ('IP') address, device name, operating system version, the + configuration of the app when utilizing our Service, the time and date + of your use of the Service, and other statistics. +
+ ++ Cookies are files with a small amount of data that are commonly used + as anonymous unique identifiers. These are sent to your browser from + the websites that you visit and are stored on your device's internal + memory. This Service does not use these 'cookies' explicitly. However, + the app may use third-party code and libraries that use 'cookies' to + collect information and improve their services. You have the option to + either accept or refuse these cookies and know when a cookie is being + sent to your device. If you choose to refuse our cookies, you may not + be able to use some portions of this Service. +
+ ++ We may employ third-party companies and individuals due to the + following reasons: +
++ We want to inform users of this Service that these third parties have + access to their Personal Information. The reason is to perform the + tasks assigned to them on our behalf. However, they are obligated not + to disclose or use the information for any other purpose. +
+ ++ We value your trust in providing us your Personal Information, thus we + are striving to use commercially acceptable means of protecting it. + But remember that no method of transmission over the internet, or + method of electronic storage is 100% secure and reliable, and we + cannot guarantee its absolute security. +
+ ++ This Service may contain links to other sites. If you click on a + third-party link, you will be directed to that site. Note that these + external sites are not operated by us. Therefore, we strongly advise + you to review the Privacy Policy of these websites. We have no control + over and assume no responsibility for the content, privacy policies, + or practices of any third-party sites or services. +
+ ++ These Services do not address anyone under the age of 13. We do not + knowingly collect personally identifiable information from children + under 13 years of age. In the case we discover that a child under 13 + has provided us with personal information, we immediately delete this + from our servers. If you are a parent or guardian and you are aware + that your child has provided us with personal information, please + contact us so that we will be able to do the necessary actions. +
+ ++ We may update our Privacy Policy from time to time. Thus, you are + advised to review this page periodically for any changes. We will + notify you of any changes by posting the new Privacy Policy on this + page. +
++ This policy is effective as of 2022-10-09 +
+ ++ If you have any questions or suggestions about our Privacy Policy, do + not hesitate to contact us at + support@httpsms.com. +
++ On this page, you can search all your messages by phone number, + message type, and message status and even using the content of the + SMS message. You will also be able to bulk delete messages and + even export your messages in a CSV file. +
+{{ item.content }}
+
+
+ Use your API Key in the
+ Scan this QR code with the + httpSMS app + on your Android phone to login. +
+ ++ Webhooks allow us to send events to your server for example when + the android phone receives an SMS message we can forward the + message to your server. +
+
+ Send and receive SMS messages without leaving your discord server
+ with the httpSMS discord app using the
+
+ List of mobile phones which are registered for sending and + receiving SMS messages. +
++ Create availability schedules and attach them to each phone. + Outgoing messages sent outside the schedule window are queued and + delivered when the schedule opens according to your + configured send rate. +
++ Manage the email notifications which you receive from httpSMS. + Feel free to turn on/off individual notifications anytime so you + don't get overloaded with emails +
+
+ Your messages are permanently deleted once they exceed the max
+ retention period below, counted from when the message was sent or
+ received. You can always delete your messages manually on the
+
+ You cannot delete your account because you have an active
+ subscription on httpSMS.
+
+ You can delete all your data on httpSMS by clicking the button + below. This action is irreversible and all your data will + be permanently deleted from the httpSMS database instantly and it + cannot be recovered. +
++ Click the button below to add the httpSMS bot to your discord + server. You need to do this so we can have permission to send + and receive messages on your discord server. +
++ By downloading or using the app and using the HTTP SMS service, these + terms will automatically apply to you – you should make sure therefore + that you read them carefully before using the app. You're not allowed + to copy or modify the app, any part of the app, or our trademarks in + any way. You're not allowed to attempt to extract the source code of + the app, and you also shouldn't try to translate the app into other + languages or make derivative versions. The app itself, and all the + trademarks, copyright, database rights, and other intellectual + property rights related to it, still belong to Ndole Studio. +
++ Ndole Studio is committed to ensuring that the app is as useful and + efficient as possible. For that reason, we reserve the right to make + changes to the app or to charge for its services, at any time and for + any reason. We will never charge you for the app or its services + without making it very clear to you exactly what you're paying for. +
++ The HTTP SMS app stores and processes personal data that you have + provided to us, to provide our Service. It's your responsibility to + keep your phone and access to the app secure. We therefore recommend + that you do not jailbreak or root your phone, which is the process of + removing software restrictions and limitations imposed by the official + operating system of your device. It could make your phone vulnerable + to malware/viruses/malicious programs, compromise your phone's + security features and it could mean that the HTTP SMS app won't work + properly or at all. +
++ HTTP SMS does use third-party services that declare their Terms and + Conditions. Link to Terms and Conditions of third-party service + providers used by the app: +
++ You should be aware that there are certain things that Ndole Studio + will not take responsibility for. Certain functions of the app will + require the app to have an active internet connection. The connection + can be Wi-Fi or provided by your mobile network provider, but Ndole + Studio cannot take responsibility for the app not working at full + functionality if you don't have access to Wi-Fi, and you don't have + any of your data allowance left. +
++ If you're using the app outside of an area with Wi-Fi, you should + remember that the terms of the agreement with your mobile network + provider will still apply. As a result, you may be charged by your + mobile provider for the cost of data for the duration of the + connection while accessing the app, or other third-party charges. In + using the app, you're accepting responsibility for any such charges, + including roaming data charges if you use the app outside of your home + territory (i.e. region or country) without turning off data roaming. + If you are not the bill payer for the device on which you're using the + app, please be aware that we assume that you have received permission + from the bill payer for using the app. +
++ Along the same lines, Ndole Studio cannot always take responsibility + for the way you use the app i.e. You need to make sure that your + device stays charged – if it runs out of battery and you can't turn it + on to avail the Service, Ndole Studio cannot accept responsibility. +
++ With respect to Ndole Studio's responsibility for your use of the app, + when you're using the app, it's important to bear in mind that + although we endeavor to ensure that it is updated and correct at all + times, we do rely on third parties to provide information to us so + that we can make it available to you. Ndole Studio accepts no + liability for any loss, direct or indirect, you experience as a result + of relying wholly on this functionality of the app. +
++ At some point, we may wish to update the app. The app is currently + available on Android – the requirements for the system (and for any + additional systems we decide to extend the availability of the app to) + may change, and you'll need to download the updates if you want to + keep using the app. Ndole Studio does not promise that it will always + update the app so that it is relevant to you and/or works with the + Android version that you have installed on your device. However, you + promise to always accept updates to the application when offered to + you. We may also wish to stop providing the app, and may terminate use + of it at any time without giving notice of termination to you. Unless + we tell you otherwise, upon any termination, (a) the rights and + licenses granted to you in these terms will end; (b) you must stop + using the app, and (if needed) delete it from your device. +
+ ++ We may update our Terms and Conditions from time to time. Thus, you + are advised to review this page periodically for any changes. We will + notify you of any changes by posting the new Terms and Conditions on + this page. +
++ These terms and conditions are effective as of 2022-10-09 +
+ ++ If you have any questions or suggestions about our Terms and + Conditions, do not hesitate to contact us at + support@httpsms.com. +
++ Don't hesitate to + message us on Discord + if you have any questions +
+code')
+ })
+
+ await t.test('wraps multiline triple-backtick code blocks', () => {
+ assert.equal(
+ formatWhatsappText('```\nline one\nline two\n```'),
+ '\nline one\nline two\n',
+ )
+ })
+
+ await t.test('does not reformat markdown characters inside code', () => {
+ assert.equal(formatWhatsappText('`a *b* c`'), 'a *b* c')
+ })
+})
+
+test('formatWhatsappText - links', async (t) => {
+ await t.test('linkifies a raw URL', () => {
+ assert.equal(
+ formatWhatsappText('visit https://example.com today'),
+ 'visit https://example.com today',
+ )
+ })
+
+ await t.test('strips unbalanced trailing punctuation from a URL', () => {
+ assert.equal(
+ formatWhatsappText('Check this out (https://example.com).'),
+ 'Check this out (https://example.com).',
+ )
+ })
+
+ await t.test(
+ 'keeps a balanced trailing parenthesis as part of the URL',
+ () => {
+ assert.equal(
+ formatWhatsappText('See https://example.com/page_(v2) for details.'),
+ 'See https://example.com/page_(v2) for details.',
+ )
+ },
+ )
+
+ await t.test(
+ 'preserves a legitimate trailing marker character that is part of the URL',
+ () => {
+ assert.equal(
+ formatWhatsappText('Download https://example.com/file_name_ now'),
+ 'Download https://example.com/file_name_ now',
+ )
+ },
+ )
+
+ await t.test('bolds a URL wrapped in asterisks', () => {
+ assert.equal(
+ formatWhatsappText('*https://example.com*'),
+ 'https://example.com',
+ )
+ })
+})
+
+test('formatWhatsappText - escaping and placeholders', async (t) => {
+ await t.test('escapes HTML special characters', () => {
+ assert.equal(
+ formatWhatsappText(''),
+ '<script>alert(1)</script>',
+ )
+ })
+
+ await t.test(
+ 'does not let literal placeholder-like text leak a link into the output',
+ () => {
+ const input = 'no links here, just text'
+ assert.equal(formatWhatsappText(input), input)
+ },
+ )
+
+ await t.test('two calls do not interfere with each other', () => {
+ const first = formatWhatsappText('visit https://a.example today')
+ const second = formatWhatsappText('visit https://b.example today')
+ assert.match(first, /href="https:\/\/a\.example"/)
+ assert.match(second, /href="https:\/\/b\.example"/)
+ assert.doesNotMatch(first, /https:\/\/b\.example/)
+ })
+})
diff --git a/web/app/utils/whatsapp-format.ts b/web/app/utils/whatsapp-format.ts
new file mode 100644
index 000000000..202a12355
--- /dev/null
+++ b/web/app/utils/whatsapp-format.ts
@@ -0,0 +1,200 @@
+/**
+ * Returns true when the given phone notification token is a URL (HTTP notification
+ * transport) instead of an FCM token.
+ */
+export function isUrlNotificationToken(
+ fcmToken?: string | null | undefined,
+): boolean {
+ if (!fcmToken) {
+ return false
+ }
+ return fcmToken.trim().toLowerCase().startsWith('https://')
+}
+
+function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+}
+
+// Character class (as a regex source string) that is treated as a valid
+// boundary around a formatting marker: whitespace, common punctuation, or
+// the start/end of the string.
+// Other formatting markers and generated tags (<>) also count so that nested
+// formatting such as `_*text*_` works.
+const BOUNDARY_CHARS = String.raw`\s.,!?;:'"()\[\]{}<>*_~-`
+
+function withBoundaries(pattern: string): RegExp {
+ return new RegExp(
+ `(?<=^|[${BOUNDARY_CHARS}])${pattern}(?=$|[${BOUNDARY_CHARS}])`,
+ 'g',
+ )
+}
+
+const CODE_BLOCK_PATTERN = withBoundaries('```([^`]+)```')
+const INLINE_CODE_PATTERN = withBoundaries('`(\\S(?:[^`\\n]*\\S)?)`')
+const BOLD_PATTERN = withBoundaries('\\*(\\S(?:[^*\\n]*\\S)?)\\*')
+const ITALIC_PATTERN = withBoundaries('_(\\S(?:[^_\\n]*\\S)?)_')
+const STRIKE_PATTERN = withBoundaries('~(\\S(?:[^~\\n]*\\S)?)~')
+
+// Characters that are stripped off the end of a matched URL because they are
+// Sentence punctuation that is always treated as trailing text, never part
+// of a URL (balanced closing parentheses are handled separately below).
+const URL_TRAILING_PUNCTUATION = new Set([
+ ',',
+ '.',
+ ';',
+ ':',
+ '!',
+ '?',
+ "'",
+ '"',
+ ']',
+ '}',
+])
+
+// WhatsApp formatting-marker characters. A trailing one of these is only
+// stripped from a URL when it pairs with a matching opening delimiter
+// immediately before the URL (e.g. the closing `*` in `*https://x.com*`),
+// so legitimate URL characters like the trailing `_` in `.../file_name_`
+// are preserved.
+const URL_TRAILING_MARKERS = new Set(['*', '_', '~', '`'])
+
+/**
+ * Splits a raw URL match into the actual URL and any trailing characters
+ * that aren't part of it. Closing parentheses are balance-checked so URLs
+ * such as `https://example.com/page_(v2)` keep their closing parenthesis.
+ */
+function splitTrailingPunctuation(
+ raw: string,
+ precedingChar: string | undefined,
+): {
+ url: string
+ trailing: string
+} {
+ let end = raw.length
+ while (end > 0) {
+ const char = raw[end - 1] as string
+ if (char === ')') {
+ const opens = (raw.slice(0, end).match(/\(/g) ?? []).length
+ const closes = (raw.slice(0, end).match(/\)/g) ?? []).length
+ if (closes <= opens) break
+ end--
+ continue
+ }
+ if (!URL_TRAILING_PUNCTUATION.has(char)) break
+ end--
+ }
+
+ if (end > 0) {
+ const char = raw[end - 1] as string
+ if (URL_TRAILING_MARKERS.has(char) && precedingChar === char) {
+ end--
+ }
+ }
+
+ return { url: raw.slice(0, end), trailing: raw.slice(end) }
+}
+
+/**
+ * Stores HTML fragments (links, code spans) behind unique placeholder
+ * tokens so that the markdown replacements that run afterwards don't touch
+ * them. Each store instance uses a random per-call nonce so that literal
+ * text in the message can never collide with a placeholder token.
+ */
+function createPlaceholderStore() {
+ const nonce = Math.random().toString(36).slice(2)
+ const open = `\uE000${nonce}:`
+ const close = `:${nonce}\uE000`
+ const store: string[] = []
+ const pattern = new RegExp(`${open}(\\d+)${close}`, 'g')
+
+ return {
+ protect(html: string): string {
+ store.push(html)
+ return `${open}${store.length - 1}${close}`
+ },
+ restore(text: string): string {
+ return text.replace(pattern, (_, idx: string) => store[Number(idx)] ?? '')
+ },
+ }
+}
+
+type PlaceholderStore = ReturnType${content}`),
+ )
+ .replace(INLINE_CODE_PATTERN, (_, content: string) =>
+ placeholders.protect(`${content}`),
+ )
+}
+
+/**
+ * Converts WhatsApp-style formatted text into sanitized HTML.
+ * Supported syntax (each marker pair must be surrounded by whitespace,
+ * punctuation, or the start/end of the string, and must not have internal
+ * leading/trailing whitespace, matching WhatsApp's own formatting rules):
+ * - *bold*
+ * - _italic_
+ * - ~strikethrough~
+ * - `code` / ```code block``` (code content is never re-formatted)
+ * - "> " quoted lines (the space after ">" is required)
+ * - raw URLs, turned into clickable links
+ */
+export function formatWhatsappText(text: string): string {
+ const escaped = escapeHtml(text ?? '')
+ const placeholders = createPlaceholderStore()
+
+ const withLinks = linkifyUrls(escaped, placeholders)
+
+ const withQuotes = withLinks
+ .split('\n')
+ .map((line) => {
+ const quoteMatch = line.match(/^> (.*)$/)
+ if (quoteMatch) {
+ return `${quoteMatch[1]}`
+ }
+ return line
+ })
+ .join('\n')
+
+ const withCode = extractCodeSpans(withQuotes, placeholders)
+
+ const formatted = withCode
+ .replace(BOLD_PATTERN, '$1')
+ .replace(ITALIC_PATTERN, '$1')
+ .replace(STRIKE_PATTERN, '- Start sending messages -
-- Install the mobile app on your android phone to start sending messages -
-- {{ $store.getters.getOwner | phoneCountry }} -
-
- This is the summary of the sent messages and received messages in
- {{
- $store.getters.getBillingUsage.start_timestamp | billingPeriod
- }}.
-
Messages Sent
-Messages Received
-Total Cost
-- Summary of all the sent and received messages in the past 12 - months -
-
-let apiKey = "Get API Key from https://httpsms.com/settings";
-
-fetch('https://api.httpsms.com/v1/messages/send', {
- method: 'POST',
- headers: {
- 'x-api-key': apiKey,
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- "content": "This is a sample text message",
- "from": "+18005550199",
- "to": "+18005550100"
- })
-})
-.then(res => res.json())
-.then((data) => console.log(data));
-
-
-
-<?php
-// initialize guzzle client https://github.com/guzzle/guzzle
-$client = new GuzzleHttp\Client();
-
-$apiKey = "Get API Key from https://httpsms.com/settings";
-
-$res = $client->request('POST', 'https://api.httpsms.com/v1/messages/send', [
- 'headers' => [
- 'x-api-key' => $apiKey,
- ],
- 'json' => [
- 'content' => 'This is a sample text message',
- 'from' => "+18005550199",
- 'to' => '+18005550100'
- ]
-]);
-
-echo $res->getBody();
-
-
-
-import "github.com/NdoleStudio/httpsms-go"
-
-client := htpsms.New(htpsms.WithAPIKey(/* API Key from https://httpsms.com/settings */))
-
-client.Messages.Send(context.Background(), &httpsms.MessageSendParams{
- Content: "This is a sample text message",
- From: "+18005550199",
- To: "+18005550100",
-})
-
-
-
-var client = HttpClient.newHttpClient();
-
-var apiKey = "Get API Key from https://httpsms.com/settings";
-
-JSONObject request = new JSONObject();
-request.put("content", "This is a sample text message");
-request.put("from", "+18005550199")
-request.put("to", "+18005550100")
-
-// create a request
-var request = HttpRequest.newBuilder()
- .uri(URI.create("https://api.httpsms.com/v1/messages/send"))
- .header("accept", "application/json")
- .header("x-api-key", apiKey)
- .setEntity(new StringEntity(request.toString()))
- .POST()
- .build();
-
-// use the client to send the request
-var response = client.send(request, new JsonBodyHandler<>(APOD.class));
-
-// the response:
-System.out.println(response.body().get());
-
-
-
-curl --location --request POST 'https://api.httpsms.com/v1/messages/send' \
---header 'x-api-key: Get API Key from https://httpsms.com/settings' \
---header 'Content-Type: application/json' \
---data-raw '{
- "from": "+18005550199",
- "to": "+18005550100",
- "content": "This is a sample text message"
-}'
-
-
-
-var client = new HttpClient();
-client.DefaultRequestHeaders.Add("x-api-key", ""/* Get API Key from https://httpsms.com/settings */);
-
-var response = await client.PostAsync(
- "https://api.httpsms.com/v1/messages/send",
- new StringContent(
- JsonSerializer.Serialize(new {
- from = "+18005550199",
- To = "+18005550100",
- Content = "This is a sample text message",
- }),
- Encoding.UTF8,
- "application/json"
- )
-);
-
-Console.WriteLine(await response.Content.ReadAsStringAsync());
-
-
- - © {{ new Date().getFullYear() }} - - {{ $store.getters.getAppData.name }} -
-- Login to the HTTP SMS dashboard -
-- Ndole Studio built the HTTP SMS service as an open source project. - This SERVICE is provided by Ndole Studio and is intended for use as - is. This page is used to inform visitors regarding our policies with - the collection, use, and disclosure of Personal Information if - anyone decided to use our Service. If you choose to use our Service, - then you agree to the collection and use of information in relation - to this policy. The Personal Information that we collect is used for - providing and improving the Service. We will not use or share your - information with anyone except as described in this Privacy Policy. - The terms used in this Privacy Policy have the same meanings as in - our Terms and Conditions, which are accessible at HTTP SMS unless - otherwise defined in this Privacy Policy. -
-- For a better experience, while using our Service, we may require you - to provide us with certain personally identifiable information, - including but not limited to email,full name. The information that - we request will be retained by us and used as described in this - privacy policy. -
-- HTTP SMS does use third-party services that may collect information - used to identify you. Link to the privacy policy of third-party - service providers used by HTTP SMS -
-- We want to inform you that whenever you use our Service, in a case - of an error in the app we collect data and information (through - third-party products) on your phone and computer called Log Data. - This Log Data may include information such as your device Internet - Protocol (“IP”) address, device name, operating system version, the - configuration of the app when utilizing our Service, the time and - date of your use of the Service, and other statistics. -
-- Cookies are files with a small amount of data that are commonly used - as anonymous unique identifiers. These are sent to your browser from - the websites that you visit and are stored on your device's internal - memory. This Service does not use these “cookies” explicitly. - However, the app may use third-party code and libraries that use - “cookies” to collect information and improve their services. You - have the option to either accept or refuse these cookies and know - when a cookie is being sent to your device. If you choose to refuse - our cookies, you may not be able to use some portions of this - Service. -
-- We may employ third-party companies and individuals due to the - following reasons: -
-- We want to inform users of this Service that these third parties - have access to their Personal Information. The reason is to perform - the tasks assigned to them on our behalf. However, they are - obligated not to disclose or use the information for any other - purpose. -
-- We value your trust in providing us your Personal Information, thus - we are striving to use commercially acceptable means of protecting - it. But remember that no method of transmission over the internet, - or method of electronic storage is 100% secure and reliable, and we - cannot guarantee its absolute security. -
-- This Service may contain links to other sites. If you click on a - third-party link, you will be directed to that site. Note that these - external sites are not operated by us. Therefore, we strongly advise - you to review the Privacy Policy of these websites. We have no - control over and assume no responsibility for the content, privacy - policies, or practices of any third-party sites or services. -
-- These Services do not address anyone under the age of 13. We do not - knowingly collect personally identifiable information from children - under 13 years of age. In the case we discover that a child under 13 - has provided us with personal information, we immediately delete - this from our servers. If you are a parent or guardian and you are - aware that your child has provided us with personal information, - please contact us so that we will be able to do the necessary - actions. -
-- We may update our Privacy Policy from time to time. Thus, you are - advised to review this page periodically for any changes. We will - notify you of any changes by posting the new Privacy Policy on this - page. -
-This policy is effective as of 2022-10-09
-- If you have any questions or suggestions about our Privacy Policy, - do not hesitate to contact us at - support@httpsms.com. -
-
- Use your API Key in the x-api-key HTTP Header when
- sending requests to
- https://api.httpsms.com endpoints.
-
- List of mobile phones which are registered for sending and - receiving SMS messages. -
-- By downloading or using the app and using the HTTP SMS service, - these terms will automatically apply to you – you should make sure - therefore that you read them carefully before using the app. You’re - not allowed to copy or modify the app, any part of the app, or our - trademarks in any way. You’re not allowed to attempt to extract the - source code of the app, and you also shouldn’t try to translate the - app into other languages or make derivative versions. The app - itself, and all the trademarks, copyright, database rights, and - other intellectual property rights related to it, still belong to - Ndole Studio. -
-- Ndole Studio is committed to ensuring that the app is as useful and - efficient as possible. For that reason, we reserve the right to make - changes to the app or to charge for its services, at any time and - for any reason. We will never charge you for the app or its services - without making it very clear to you exactly what you’re paying for. -
-- The HTTP SMS app stores and processes personal data that you have - provided to us, to provide our Service. It’s your responsibility to - keep your phone and access to the app secure. We therefore recommend - that you do not jailbreak or root your phone, which is the process - of removing software restrictions and limitations imposed by the - official operating system of your device. It could make your phone - vulnerable to malware/viruses/malicious programs, compromise your - phone’s security features and it could mean that the HTTP SMS app - won’t work properly or at all. -
-- HTTP SMS does use third-party services that declare their Terms and - Conditions. Link to Terms and Conditions of third-party service - providers used by the app -
-- You should be aware that there are certain things that Ndole Studio - will not take responsibility for. Certain functions of the app will - require the app to have an active internet connection. The - connection can be Wi-Fi or provided by your mobile network provider, - but Ndole Studio cannot take responsibility for the app not working - at full functionality if you don’t have access to Wi-Fi, and you - don’t have any of your data allowance left. -
-- If you’re using the app outside of an area with Wi-Fi, you should - remember that the terms of the agreement with your mobile network - provider will still apply. As a result, you may be charged by your - mobile provider for the cost of data for the duration of the - connection while accessing the app, or other third-party charges. In - using the app, you’re accepting responsibility for any such charges, - including roaming data charges if you use the app outside of your - home territory (i.e. region or country) without turning off data - roaming. If you are not the bill payer for the device on which - you’re using the app, please be aware that we assume that you have - received permission from the bill payer for using the app. -
-- Along the same lines, Ndole Studio cannot always take responsibility - for the way you use the app i.e. You need to make sure that your - device stays charged – if it runs out of battery and you can’t turn - it on to avail the Service, Ndole Studio cannot accept - responsibility. -
-- With respect to Ndole Studio’s responsibility for your use of the - app, when you’re using the app, it’s important to bear in mind that - although we endeavor to ensure that it is updated and correct at all - times, we do rely on third parties to provide information to us so - that we can make it available to you. Ndole Studio accepts no - liability for any loss, direct or indirect, you experience as a - result of relying wholly on this functionality of the app. -
-- At some point, we may wish to update the app. The app is currently - available on Android – the requirements for the system(and for any - additional systems we decide to extend the availability of the app - to) may change, and you’ll need to download the updates if you want - to keep using the app. Ndole Studio does not promise that it will - always update the app so that it is relevant to you and/or works - with the Android version that you have installed on your device. - However, you promise to always accept updates to the application - when offered to you, We may also wish to stop providing the app, and - may terminate use of it at any time without giving notice of - termination to you. Unless we tell you otherwise, upon any - termination, (a) the rights and licenses granted to you in these - terms will end; (b) you must stop using the app, and (if needed) - delete it from your device. -
-- We may update our Terms and Conditions from time to time. Thus, you - are advised to review this page periodically for any changes. We - will notify you of any changes by posting the new Terms and - Conditions on this page. -
-These terms and conditions are effective as of 2022-10-09
-- If you have any questions or suggestions about our Terms and - Conditions, do not hesitate to contact us at - support@httpsms.com. -
-Send and receive messages using our API
-