Containerized SendPortal email campaign manager with Docker Compose, featuring automated TLS via Caddy reverse proxy.
- 🚀 Quick Start
- 🔐 Secrets
- ⏰ Cron Jobs
- 📝 Logging
- 🔒 HTTPS via Reverse Proxy
- 🧹 Cleanup (Docker Compose)
- 🛠️ Handy SendPortal Commands
- 📝 Todos
- Create the DB secret & Env files
cp secrets/env.example secrets/.env vim /secrets/db_password
- Copy
Caddyfile.example
toCaddyfile
. Note: example uses Let's Encrypt's stage URL. docker compose up -d --build
- Go to
localhost:8080/setup
to complete the configuration. docker compose down -v
when you want to destroy everything.
Required files in secrets
:
db_password
- MySQL root password.env
- SendPortal configuration (If you don't have one, setup will create one)
Campaign queues are processed in SendPortal by cron jobs. First make sure the jobs
database table exists:
php artisan queue:table # Create the jobs table
php artisan migrate --force
There are many ways to run periodic jobs. Containerizing them is an option. Perhaps the simplest and most reliable method is to add the following to the compose node's crontab:
crontab -e
and paste in:
*/2 * * * * docker exec sendportal-app php /var/www/html/artisan queue:work --queue=sendportal-webhook-process --stop-when-empty
*/2 * * * * docker exec sendportal-app php /var/www/html/artisan queue:work --queue=sendportal-message-dispatch --stop-when-empty
* * * * * docker exec sendportal-app php /var/www/html/artisan schedule:run >> /var/log/sendportal-cron.log 2>&1
By default application logs end up in /var/www/html/storage/logs
inside the container.
You can handle that in a couple ways. This demo just uses a volume mount.
It would be nicer to send these to stdout instead, which we can do by setting LOG_CHANNEL
env var or overriding logging.php
with our own.
View logs that go to stdout with docker logs sendportal-app
SendPortal runs behind Caddy server, which handles TLS termination and acts as a reverse proxy. Caddy uses Let's Encrypt to get you a TLS certificate. The example Caddy Conf uses a
staging Let's Encrypt endpoint to avoid rate limits. When you get a stable system working, remove the stage endpoint from the config and run caddy reload
to get a signed TLS cert chain.
If you have a publicly resolvable domain, edit compose.yaml
APP_URL: https://localhost
For custom Caddy configurations, refer to the Caddy documentation.
Remove all containers and volumes:
docker compose down -v
Run these from within the sendportal application container if needed. The Entrypoint script handles most of this for you.
Command | Description |
---|---|
php artisan key:generate |
Generate new application key |
php artisan sp:install |
Run setup programmatically |
php artisan schedule:run |
Process queued jobs manually |
php artisan queue:table |
Create the jobs table |
php artisan migrate |
Run DB migrations |
-
It would be rad to have a fully automated setup without needing to run an interactive CLI command or web based setup wizard. Can potentially accomplish this by injecting some SQL commands via the entrypoint script, or making artisan sp:install a little more robust.
-
If .env file is specified we probably don't need to get db_password from secrets. It's redundant. Clean this up.
-
Lots of the entrypoint.sh stuff should actually be in an init container that finishes before the app container starts. Refactor & test that.
-
Adding a Redis container to process mail queues (rather than sql db) seemed like overkill for this demo, but could be nice to add.
-
Add k8s manifest files and/or a helm chart with proper init and cron job containers.