textbee Logotextbee.dev
Save 44% with yearly billing.View Plans
Documentation

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

ComponentRole
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.
MongoDBUsers, devices, messages, webhook subscriptions and plans
RedisBackground job queues: the SMS send queue, webhook deliveries and other jobs
Firebase Cloud MessagingPush 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 serverEmail verification, password resets and limit notices

Before you start

You need:

  • A server with Docker, or with Node.js, pnpm and pm2.
  • 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.json for 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.

  1. Clone the repository:

    Shell
    git clone https://github.com/textbee/textbee.git
    cd textbee
  2. Copy both example environment files:

    Shell
    cp web/.env.example web/.env
    cp api/.env.example api/.env
  3. Fill in api/.env:

    VariableValue
    MONGO_URIThe example value points at the textbee-db container. Change the user and password, and set the same values in MONGO_ROOT_USER and MONGO_ROOT_PASS.
    JWT_SECRETA long random string
    FRONTEND_URLThe public URL of your dashboard, for example https://sms.example.com
    FIREBASE_*The fields from your Firebase service account JSON file
    MAIL_*, ADMIN_EMAILYour SMTP server
    USE_SMS_QUEUEtrue to use Redis for the send queue. Scheduled messages need it.
  4. Fill in web/.env:

    VariableValue
    NEXT_PUBLIC_SITE_URLThe public URL of your dashboard
    NEXT_PUBLIC_API_BASE_URLYour API URL with /api/v1, for example https://sms.example.com/api/v1
    NEXTAUTH_SECRETA long random string
    NEXTAUTH_URLThe public URL of your dashboard
    MAIL_*Your SMTP server
  5. Start the stack from the repository root:

    Shell
    docker compose up -d

This command starts five containers:

ContainerWhat it isHost port
textbee-dbMongoDB. It creates the textbee database.27018
textbee-mongo-expressA web admin UI for MongoDB. Optional.8081
textbee-apiThe API3001
textbee-webThe dashboard3000
textbee-redisRedis6379

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:

Shell
docker compose down

Do 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.

  1. Open the android/ directory of the repository.

  2. Replace every occurrence of textbee.dev with your domain. The API address is API_BASE_URL in the prod flavor of android/app/build.gradle. Set it to your API URL with a trailing slash, for example https://sms.example.com/api/v1/.

  3. In Firebase, add an Android app with the package name com.vernu.sms, or the package name you set. Download its google-services.json and put it at android/app/src/prod/google-services.json.

  4. Build the release APK:

    Shell
    ./gradlew assembleProdRelease

    The APK is in android/app/build/outputs/apk/prod/release/. The README uses ./gradlew assembleRelease, which builds both flavors. The dev flavor then also needs its own google-services.json.

  5. 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.

  1. Install pnpm, pm2 and Caddy on the server. Set up MongoDB and Redis, or use hosted versions.

  2. Build the API:

    Shell
    cd api
    cp .env.example .env
    pnpm install
    pnpm build

    Set MONGO_URI and REDIS_URL in api/.env to your own MongoDB and Redis addresses.

  3. Start the API with pm2:

    Shell
    pm2 start dist/main.js --name textbee-api
  4. Build and start the dashboard:

    Shell
    cd ../web
    cp .env.example .env
    pnpm install
    pnpm build
    pm2 start "pnpm start" --name textbee-web
  5. 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
    }
  6. 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/v1 suffix 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

  1. Pull the new code:

    Shell
    git pull
  2. Compare api/.env.example and web/.env.example with your .env files. Add any new variables.

  3. Rebuild and restart. With Docker Compose, run docker compose up -d --build. On a VPS, run pnpm install and pnpm build in api/ and web/, then pm2 restart textbee-api textbee-web.

  4. 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 itThe textbee teamYou
UpdatesAutomatic for the API and dashboardYou pull, rebuild and redeploy
Android appThe published APK from textbee.dev/downloadYour own build for your domain
DataOn textbee serversIn your own database
SupportEmail support and DiscordDiscord and GitHub issues
Cost modelA plan with a monthly message allowanceYour 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.

Next steps