NetSendo

Quick Start

Install self-hosted NetSendo with Docker Compose: one repository, one .env file and one command.

Written for NetSendo 2.7.0, following install.sh, DOCKER_INSTALL.md and docker-compose.yml of the repository.

Requirements

MinimumRecommended
Docker20.1024.0
Docker Composev2.0v2.20
RAM2 GB4 GB
Disk space5 GB10 GB

You also need Git and a terminal on the server.

1. Clone the repository

git clone https://github.com/NetSendo/NetSendo.git
cd NetSendo
Or let the install script do steps 1 to 3
install.sh creates a netsendo folder with the configuration files, writes a .env with a new APP_KEY and a random database password, starts the stack on http://localhost:5029 and waits until it answers. Set VERSION=2.7.0 before bash to install a specific release.
curl -fsSL https://raw.githubusercontent.com/NetSendo/NetSendo/main/install.sh | bash

2. Create the configuration file

cp .env.example .env

# Put a new key on the APP_KEY line
sed -i.bak "s|^APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|" .env

# Set APP_URL and a strong DB_PASSWORD
nano .env

The settings to fill in before the first start:

.env
APP_KEY=base64:YOUR_GENERATED_KEY
APP_URL=http://localhost:5029

DB_DATABASE=netsendo_prod
DB_USERNAME=netsendo_user
DB_PASSWORD=YOUR_SECURE_PASSWORD
  • APP_KEY encrypts stored passwords and keys. Set it once and keep it: the containers mount .env read-only, so they cannot write a key into it for you.
  • DB_PASSWORD is also the MySQL root password. MySQL takes it on its very first start, so choose it now.
  • APP_URL is the address people will use. Keep http://localhost:5029 for a local test; for a server, use its HTTPS address (see going live).
Create .env before starting
If .env does not exist when you run docker compose up -d, Docker creates a folder named .env in its place and the application does not start.

The other variables are explained in Configuration.

3. Start NetSendo

docker compose up -d

# Watch the first start (migrations run automatically)
docker compose ps
docker compose logs -f app

The first start pulls the ghcr.io/netsendo/netsendo image and starts these services: the application, the queue worker, the scheduler, the Reverb WebSocket server, nginx, MySQL 8.4, Redis 7 and Mailpit. The application creates the database tables on its first start.

ServiceAddress
NetSendohttp://localhost:5029
Mailpit (test emails)http://localhost:5031
Reverb (WebSocket)http://localhost:8085

4. Create the administrator and activate a license

  1. Open http://localhost:5029 and register. The first account becomes the administrator; after that, registration is closed and the administrator adds further users.
  2. The license page opens. For SILVER (free), enter your email address and the license is activated automatically.
  3. For GOLD, choose Buy GOLD: payment opens on netsendo.com in a new tab, and GOLD switches on as soon as the payment is confirmed. A key you already have goes in under I have a license key.

Then add a mailbox under Settings → Mailboxes so NetSendo can send, and an SMS provider if you need SMS. Until then, a test send of a campaign fails with “No mailbox is configured”, and emails NetSendo sends through the MAIL_* settings, such as team invitations, land in Mailpit.

5. Going live on a server

  • NetSendo listens on 127.0.0.1:5029 only. Put a reverse proxy with HTTPS (nginx or Caddy) in front of it and forward /app/ to port 8085 for WebSockets.
  • Set APP_URL to the HTTPS address, VITE_REVERB_HOST to your domain, VITE_REVERB_PORT=443 and VITE_REVERB_SCHEME=https, then run docker compose up -d --force-recreate. The image already contains the built frontend; do not run npm run build in the container.
  • Point the MAIL_* variables at a real SMTP server instead of Mailpit.
  • Register the administrator before the instance is reachable from the internet.

The nginx configuration and all variables are in Configuration → HTTPS and a reverse proxy.

Updating

cd NetSendo
git pull                     # new docker-compose.yml, docker-entrypoint.sh and nginx config
docker compose pull
docker compose up -d
docker compose logs -f app   # migrations run here; scheduler, queue and reverb wait for them

Back up the database first (see Backups): the app container runs the database migrations when it starts, and they cannot be undone. The scheduler, queue and Reverb containers wait until the migrations are done. To stay on one release instead of latest, set the image version:

NETSENDO_VERSION=2.7.0 docker compose up -d

docker compose pull updates the images, not your docker-compose.yml or docker/php/docker-entrypoint.sh, which the compose file mounts over the image's own copy. In a git checkout git pull brings them. An installation made with install.sh has no git checkout; download them from the release instead (your old copies stay as .bak files, so you can carry over your own changes). The script also keeps the image version in .env, so move NETSENDO_VERSION to the new release or to latest:

cd netsendo
V=main   # or a release tag such as v2.7.0
for f in docker-compose.yml docker/php/docker-entrypoint.sh docker/php/uploads.ini docker/nginx/default.conf; do
  cp "$f" "$f.bak" 2>/dev/null
  curl -fsSL "https://raw.githubusercontent.com/NetSendo/NetSendo/$V/$f" -o "$f"
done
chmod +x docker/php/docker-entrypoint.sh

# install.sh wrote the installed version into .env: move it to the new release (or latest)
sed -i.bak "s|^NETSENDO_VERSION=.*|NETSENDO_VERSION=2.7.0|" .env

docker compose pull
docker compose up -d

Read the release notes for changes to that file, such as the queue worker fix described in Configuration.

Backups

# Database (the db container knows its root password and database name)
docker compose exec -T db sh -c 'exec mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' > backup.sql

# Uploaded files
docker compose cp app:/var/www/storage/app ./backup-storage/

# Restore the database
docker compose exec -T db sh -c 'exec mysql -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' < backup.sql
VolumeHoldsBack up?
dbdataMySQL databaseYes
netsendo-storageUploaded filesYes
netsendo-logsApplication logsOptional
redisdataRedis cache and queueNo
netsendo-publicStatic files, synced on every startNo

Keep a copy of .env too: without its APP_KEY, encrypted data in the database cannot be read. NetSendo also runs its own backup of the database and files every night at 02:00 through the scheduler.

Optional components

  • NetSendo Mail Infrastructure, a built-in MTA for sending from dedicated IPs: docker compose --profile nmi up -d. See NMI.
  • MCP server for Claude, Cursor and VS Code: see the MCP documentation.

Troubleshooting

A .env folder was created instead of a file

docker compose down
rm -rf .env
cp .env.example .env
# set APP_KEY, APP_URL and DB_PASSWORD again, then
docker compose up -d

The app container does not start

docker compose logs app
docker compose exec db mysqladmin ping -h localhost
docker compose exec app php artisan config:clear

WebSocket errors in the browser console usually mean the reverb container is not running or the VITE_REVERB_* values do not match your address; rebuild the frontend after correcting them. More cases are in DOCKER_INSTALL.md.

Frequently asked questions

What are the system requirements for NetSendo?

Docker 20.10 or newer with Docker Compose v2, Git, at least 2 GB of RAM and 5 GB of disk space. 4 GB of RAM and 10 GB of disk are recommended, with Docker 24 and Compose v2.20 or newer.

Which port does NetSendo run on?

The web interface is on http://localhost:5029 (HTTP_PORT), bound to 127.0.0.1. Mailpit, which catches test emails, is on http://localhost:5031, and the Reverb WebSocket server on port 8085. For a public instance put a reverse proxy with HTTPS in front of port 5029.

How do I update NetSendo?

Back up the database, then in the installation folder run git pull, docker compose pull and docker compose up -d. git pull also brings the new docker-compose.yml and docker/php/docker-entrypoint.sh, which docker compose pull does not update; an installation made with install.sh downloads them from the release instead. Database migrations run when the app container starts, and the other containers wait for them. To stay on a specific release, set NETSENDO_VERSION, for example NETSENDO_VERSION=2.7.0.

Who becomes the administrator of a new installation?

The first person who registers on a fresh installation. After that, registration is closed and the administrator adds further users, so register right after the first start, before the instance is reachable from the internet.

Why did Docker create a .env folder instead of a file?

Docker creates a folder when the file it should mount does not exist. Stop the containers, delete the folder, copy .env.example to .env, fill it in and start again. Always create .env before docker compose up -d.