How I got Unsend and its SMTP relayrunning.
Unsend is an open source transactional email service that sits on top of Amazon SES.
If youâre a non-developer here for my groundbreaking thought pieces, Unsend is a thing software can use to send email.
Itâs a young alternative to Resend that makes it convenient to manage sending domains and API keys, track deliverability, and even conduct sending campaigns that I donât normally think of from a strictly transactional service.
Trying Unsend was straightforward with Coolify, but it took me a while to realize that if you want an SMTP service youâll have to run the included relay for receiving SMTP requests on their respective ports and forwarding them to the Unsend API.
The proxy runs instancesâŚ
How I got Unsend and its SMTP relayrunning.
Unsend is an open source transactional email service that sits on top of Amazon SES.
If youâre a non-developer here for my groundbreaking thought pieces, Unsend is a thing software can use to send email.
Itâs a young alternative to Resend that makes it convenient to manage sending domains and API keys, track deliverability, and even conduct sending campaigns that I donât normally think of from a strictly transactional service.
Trying Unsend was straightforward with Coolify, but it took me a while to realize that if you want an SMTP service youâll have to run the included relay for receiving SMTP requests on their respective ports and forwarding them to the Unsend API.
The proxy runs instances of smtp-server for ports 465, 2465, 25, 587, and 2587âwhich means itâs ready to handle SSL and TLS connections.
Unsendâs Docker Compose example offered a clear starting point, and Aldert Vaanderingâs Setting up Stalward on Coolify post helped me figure out what to do with the certificates unsend/smtp-proxy needs to use for SSL and TLS.
I originally started messing with Traefik labels at the server level, but it turned out I didnât need to. I followed Aldertâs lead making sure that the serverâs certificates could be available for the relay to use by appending the following to the Traefik configuration in Server â Proxy â Configuration.
traefik-certs-dumper:
image: ghcr.io/kereis/traefik-certs-dumper:latest
container_name: traefik-certs-dumper
restart: unless-stopped
depends_on:
- traefik
volumes:
- /etc/localtime:/etc/localtime:ro
- /data/coolify/proxy:/traefik:ro
- /data/coolify/certs:/output
The relay itself just needs to bind some ports and point to those Coolify certificates in order to make SSL connections:
smtp-server:
container_name: unsend-smtp-server
image: "unsend/smtp-proxy:latest"
volumes:
- /data/coolify/certs/my-unsend-url.example/key.pem:/data/certs/key.pem:ro
- /data/coolify/certs/my-unsend-url.example/cert.pem:/data/certs/cert.pem:ro
environment:
SMTP_AUTH_USERNAME: unsend
UNSEND_BASE_URL: "https://my-unsend-url.example/"
UNSEND_API_KEY_PATH: "/data/certs/key.pem"
UNSEND_API_CERT_PATH: "/data/certs/cert.pem"
ports:
- "25:25"
- "587:587"
- "2587:2587"
- "465:465"
- "2465:2465"
restart: unless-stopped
My entire Unsend stack now looks like this:
services:
postgres:
image: "postgres:16"
environment:
- "POSTGRES_USER=${SERVICE_USER_POSTGRES}"
- "POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}"
- "POSTGRES_DB=${SERVICE_DB_POSTGRES:-unsend}"
healthcheck:
test:
- CMD-SHELL
- "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"
interval: 5s
timeout: 20s
retries: 10
volumes:
- "unsend-postgres-data:/var/lib/postgresql/data"
redis:
image: "redis:7"
volumes:
- "unsend-redis-data:/data"
command:
- redis-server
- "--maxmemory-policy"
- noeviction
healthcheck:
test:
- CMD
- redis-cli
- PING
interval: 5s
timeout: 10s
retries: 20
unsend:
image: "unsend/unsend:latest"
expose:
- 3000
environment:
- SERVICE_FQDN_UNSEND_3000
- "DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${SERVICE_DB_POSTGRES:-unsend}"
- "NEXTAUTH_URL=${SERVICE_FQDN_UNSEND}"
- "NEXTAUTH_SECRET=${SERVICE_BASE64_64_NEXTAUTHSECRET}"
- "AWS_ACCESS_KEY=${AWS_ACCESS_KEY:?}"
- "AWS_SECRET_KEY=${AWS_SECRET_KEY:?}"
- "AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:?}"
- "GITHUB_ID=${GITHUB_ID}"
- "GITHUB_SECRET=${GITHUB_SECRET}"
- "REDIS_URL=redis://redis:6379"
- "NEXT_PUBLIC_IS_CLOUD=${NEXT_PUBLIC_IS_CLOUD:-false}"
- "NEXT_PUBLIC_SMTP_HOST=${NEXT_PUBLIC_SMTP_HOST}"
- "SMTP_HOST=${SMTP_HOST}"
- "API_RATE_LIMIT=${API_RATE_LIMIT:-1}"
- HOSTNAME=0.0.0.0
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test:
- CMD-SHELL
- "wget -qO- http://unsend:3000 || exit 1"
interval: 5s
retries: 10
timeout: 2s
smtp-server:
container_name: unsend-smtp-server
image: "unsend/smtp-proxy:latest"
volumes:
- /data/coolify/certs/my-unsend-url.example/key.pem:/data/certs/key.pem:ro
- /data/coolify/certs/my-unsend-url.example/cert.pem:/data/certs/cert.pem:ro
environment:
SMTP_AUTH_USERNAME: unsend
UNSEND_BASE_URL: "https://my-unsend-url.example/"
UNSEND_API_KEY_PATH: "/data/certs/key.pem"
UNSEND_API_CERT_PATH: "/data/certs/cert.pem"
ports:
- "25:25"
- "587:587"
- "2587:2587"
- "465:465"
- "2465:2465"
restart: unless-stopped
I can now configure various apps to use my Unsend URLâs ports 465 or 587, username unsend and an API key as the password, and emails happen.
Troubleshooting with the relayâs logs can be a little frustrating since issue details get flattened into [Object], but Unsend is in beta and once a logging PR gets merged that should make investigating a breeze.
Sending email to Unsend API at: https://my-unsend-url.example/api/v1/emails
Unsend API error response: { success: false, error: { issues: [ [Object] ], name: 'ZodError' } }
I love that Unsend configures SES with each new domain, so I just have to set up and verify DNS records like I would with any other service. And then I have immediate deliverability reports with message previews so I can be sure everythingâs working.
Overall itâs been a nice little learning adventure and I like consolidating a bunch of transactional mail setups into one that so far works well and inexpensively with no plan limits.
Update: thanks to Zai in Unsendâs Discord server for pointing out that I should mount only the certificate files I need to keep things cleaner. The smtp-server sectionâs volumes and environment variables are updated to reflect that.