Self-hosting textbee
Run the textbee API, dashboard and Android app on your own server with Docker Compose or a VPS, then point the SDK and MCP server at your instance.
Updated
textbee is open source, so you can run the whole stack on your own server. You run the API, the web dashboard, MongoDB and Redis, you create a Firebase project for push messages, and you build the Android app for your domain. The source is at github.com/textbee/textbee.
Self-hosting keeps all messages and account data in your own database. You also take on updates, backups and uptime.
What runs where
| Component | Role |
|---|---|
Web dashboard (Next.js, web/) | Sign-up, login, devices, API keys, webhooks, message history. Port 3000 by default. |
API (NestJS, api/) | The REST API under /api/v1, device registration, webhooks and the send queue. Port 3001 by default. |
| MongoDB | Users, devices, messages, webhook subscriptions and plans |
| Redis | Background job queues: the SMS send queue, webhook deliveries and other jobs |
| Firebase Cloud Messaging | Push service. The API uses it to wake the phone and hand it each message. |
Android app (android/) | Sends and receives SMS. You build it with your own domain and Firebase project. |
| SMTP server | Email verification, password resets and limit notices |
Before you start
You need:
- A server with Docker, or with Node.js,
pnpmandpm2. - MongoDB: the Docker Compose file starts one. You can also use your own server or MongoDB Atlas.
- Redis: the Docker Compose file starts one.
- A Firebase project with Cloud Messaging enabled. You need a service account key for the API and a
google-services.jsonfor the Android app. - A domain with HTTPS, for example
sms.example.com. The phone and your code reach the API over the internet. - An SMTP account for outgoing email. Sending SMS needs a verified email address, and the verification link goes out by email.
- Android Studio or the Android SDK to build the app.
Run with Docker Compose
The repository root has a docker-compose.yaml. It builds the API and the web dashboard from source and starts them with MongoDB and Redis.
-
Clone the repository:
Shellgit clone https://github.com/textbee/textbee.git cd textbee -
Copy both example environment files:
Shellcp web/.env.example web/.env cp api/.env.example api/.env -
Fill in
api/.env:Variable Value MONGO_URIThe example value points at the textbee-dbcontainer. Change the user and password, and set the same values inMONGO_ROOT_USERandMONGO_ROOT_PASS.JWT_SECRETA long random string FRONTEND_URLThe public URL of your dashboard, for example https://sms.example.comFIREBASE_*The fields from your Firebase service account JSON file MAIL_*,ADMIN_EMAILYour SMTP server USE_SMS_QUEUEtrueto use Redis for the send queue. Scheduled messages need it. -
Fill in
web/.env:Variable Value NEXT_PUBLIC_SITE_URLThe public URL of your dashboard NEXT_PUBLIC_API_BASE_URLYour API URL with /api/v1, for examplehttps://sms.example.com/api/v1NEXTAUTH_SECRETA long random string NEXTAUTH_URLThe public URL of your dashboard MAIL_*Your SMTP server -
Start the stack from the repository root:
Shelldocker compose up -d
This command starts five containers:
| Container | What it is | Host port |
|---|---|---|
textbee-db | MongoDB. It creates the textbee database. | 27018 |
textbee-mongo-express | A web admin UI for MongoDB. Optional. | 8081 |
textbee-api | The API | 3001 |
textbee-web | The dashboard | 3000 |
textbee-redis | Redis | 6379 |
The web image reads NEXT_PUBLIC_ values at build time. After you change one, rebuild with docker compose up -d --build.
To stop the stack, run:
docker compose downDo not expose MongoDB, mongo-express or Redis to the internet. Put only the API and the dashboard behind your reverse proxy, and block the other ports in your firewall.
Build the Android app for your domain
The published app talks to textbee.dev. Your instance needs its own build.
-
Open the
android/directory of the repository. -
Replace every occurrence of
textbee.devwith your domain. The API address isAPI_BASE_URLin theprodflavor ofandroid/app/build.gradle. Set it to your API URL with a trailing slash, for examplehttps://sms.example.com/api/v1/. -
In Firebase, add an Android app with the package name
com.vernu.sms, or the package name you set. Download itsgoogle-services.jsonand put it atandroid/app/src/prod/google-services.json. -
Build the release APK:
Shell./gradlew assembleProdReleaseThe APK is in
android/app/build/outputs/apk/prod/release/. The README uses./gradlew assembleRelease, which builds both flavors. Thedevflavor then also needs its owngoogle-services.json. -
Install the APK on the phone, then register the device with a QR code or an API key from your own dashboard. See Registering a device.
The release build signs with the debug keystore by default. Add your own signing config before you give the APK to other people.
Run on a VPS without Docker
You can also run the API and the dashboard as Node.js processes.
-
Install
pnpm,pm2and Caddy on the server. Set up MongoDB and Redis, or use hosted versions. -
Build the API:
Shellcd api cp .env.example .env pnpm install pnpm buildSet
MONGO_URIandREDIS_URLinapi/.envto your own MongoDB and Redis addresses. -
Start the API with pm2:
Shellpm2 start dist/main.js --name textbee-api -
Build and start the dashboard:
Shellcd ../web cp .env.example .env pnpm install pnpm build pm2 start "pnpm start" --name textbee-web -
Configure Caddy. This Caddyfile sends
/api/*to the API on port 3001 and everything else to the dashboard on port 3000. Caddy gets the HTTPS certificate for you.sms.example.com { reverse_proxy /api/* localhost:3001 reverse_proxy /* localhost:3000 } -
Point your domain's DNS at the server and reload Caddy.
Analytics are off by default
A self-hosted instance reports nothing to anyone by default. With no analytics variables set, the dashboard loads no analytics or ad scripts and the API sends no analytics events. Each provider is opt-in. The variables are listed in the Analytics and telemetry section of the README.
Point the SDK and MCP server at your instance
The JavaScript SDK and the MCP server read TEXTBEE_BASE_URL. Set it to your instance URL, for example https://sms.example.com.
- The
/api/v1suffix is optional. The client adds it. - Subpath deployments work, for example
https://example.com/textbee. - An invalid URL is an error. The client does not fall back to textbee.dev, so a typo never sends your API key to the public service.
The hosted MCP endpoint talks only to textbee.dev. For your own instance, run the MCP server locally with TEXTBEE_BASE_URL.
For REST calls, replace https://api.textbee.dev/api/v1 with your own API URL, for example https://sms.example.com/api/v1. Create API keys and webhooks in the dashboard of your own instance, not in the textbee.dev dashboard.
Keep your instance up to date
-
Pull the new code:
Shellgit pull -
Compare
api/.env.exampleandweb/.env.examplewith your.envfiles. Add any new variables. -
Rebuild and restart. With Docker Compose, run
docker compose up -d --build. On a VPS, runpnpm installandpnpm buildinapi/andweb/, thenpm2 restart textbee-api textbee-web. -
When the Android app changes, rebuild the APK with your domain and install it on each phone.
Back up MongoDB before each update.
Hosted or self-hosted
| Hosted (textbee.dev) | Self-hosted | |
|---|---|---|
| Who runs it | The textbee team | You |
| Updates | Automatic for the API and dashboard | You pull, rebuild and redeploy |
| Android app | The published APK from textbee.dev/download | Your own build for your domain |
| Data | On textbee servers | In your own database |
| Support | Email support and Discord | Discord and GitHub issues |
| Cost model | A plan with a monthly message allowance | Your server, database and Firebase costs |
Frequently asked questions
Do I need Redis?
Yes. The API uses Redis at REDIS_URL for its background jobs, for example webhook deliveries. USE_SMS_QUEUE controls only the send queue. With false, each send goes straight to the phone, and a send with scheduledAt returns a 400 error. Set it to true for scheduled messages and for bulk sends released in waves at the phone's Send Delay.
Why does sending return "Please verify your email to continue"?
Sending needs a verified email address. Configure SMTP so the verification email arrives, then open the link. Google sign-in also marks the email as verified.
Can I use the published app from textbee.dev/download?
No. The published app talks only to textbee.dev. Build the app with your own domain and Firebase project.
Can one phone connect to two instances?
Not with one app. Each build of the app talks to the one API address it was built with.
Do plan limits apply on my instance?
Limits come from the plan documents in your database. A new instance has no plan documents, and then the API applies no message or device limits.