Quick start
Production installs use Docker Compose. Gmail and Outlook OAuth setup is on Connections.
Requirements (production)
- Docker Engine 24+ and Docker Compose v2
- A DNS A (and optional AAAA) record for your domain
- Ports 80 and 443 open (Let's Encrypt HTTP-01 via Caddy)
Install
git clone https://github.com/contactout/campaigns.git
cd campaigns
cp .env.docker.example .envEdit .env:
| Variable | Notes |
|---|---|
DOMAIN | Hostname Caddy will serve (e.g. mail.example.com) |
ACME_EMAIL | Email for Let's Encrypt registration |
APP_URL | Must be https://YOUR_DOMAIN (include :HTTPS_PORT if not 443) |
APP_KEY | Required — see below |
HTTP_PORT / HTTPS_PORT | Host ports mapped to Caddy (default 80 / 443) |
DB_PASSWORD / DB_ROOT_PASSWORD | Strong unique passwords |
Generate APP_KEY (shared by app, queue, and scheduler):
echo "base64:$(openssl rand -base64 32)"Paste into APP_KEY=..., then:
docker compose up -d --buildConfirm HTTPS first.
docker/Caddyfilesends a one-yearStrict-Transport-Security(HSTS) header (max-age=31536000; includeSubDomains) and.env.docker.examplesetsSESSION_SECURE_COOKIE=true. Once a browser sees HSTS it refuses plain HTTP for the domain and its subdomains, so make sureDOMAINresolves to this host and Caddy obtains a certificate before real users visit. If unsure, comment out the HSTS line for the first boot, confirmhttps://YOUR_DOMAINloads with a valid certificate, then re-enable it and rundocker compose restart caddy. Adding; preload(HSTS preload list) is a separate, deliberate opt-in.
includeSubDomainscovers every subdomain ofDOMAIN. IfDOMAINis e.g.example.com, browsers will also refuse plain HTTP forwww.example.com,intranet.example.com, and so on. If any subdomain is still HTTP-only, remove; includeSubDomainsfromdocker/Caddyfilebefore deploying.
Open https://YOUR_DOMAIN and register the first user. The dashboard checklist walks you through:
- Connect an inbox (SMTP / Gmail / Outlook)
- Add contacts
- Create a campaign
System email & registration
System mail (email verification, password reset, team invitations) needs SMTP MAIL_* settings in .env (MAIL_MAILER=smtp, MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD, MAIL_FROM_ADDRESS). This is separate from the inboxes you connect for campaign sending.
- The first registered user can always register.
- Set
REGISTRATION_ENABLED=trueto allow open sign-up. Otherwise, others join through team invitations. - If mail isn't configured, verify a user from the command line:
docker compose exec app php artisan campaigns:verify-user you@example.comLocal smoke test (self-signed TLS)
Useful on a laptop without public DNS:
cp .env.docker.example .env
# DOMAIN=localhost
# HTTP_PORT=8088
# HTTPS_PORT=8443
# APP_URL=https://localhost:8443
# APP_KEY=... DB_PASSWORD=... DB_ROOT_PASSWORD=...
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --buildOpen https://localhost:8443 (or your HTTPS_PORT) and accept the certificate warning.
Day-to-day development without this stack is covered in Contributing.