Generating PDFs and emails from templates is something almost every business application ends up needing: invoices, contracts, notifications, statements⦠TemplateMaster provides a PDF & Email generation API paired with a visual template editor.
Good news: the whole stack can run locally with a single docker compose up. In this article we'll walk through the official configuration, start it, and I'll share a few tweaks to make it more robust.
π Source: official documentation β Run local environment
π§± What we're going to run
The local stack is made of 5 services:
| Service | Image | Role | Port(s) |
|---|---|---|---|
editorTemplate-front |
templatemaster/editortemplate-front |
Web UI / visual editor | 8080 |
editorTemplate-api |
templatemaster/editortemplate-api |
.NET API (PDF/email generation) | 5000 |
mssql |
mcr.microsoft.com/mssql/server:2019-latest |
Database (app + Hangfire) | 1433 |
rabbitmq-service |
rabbitmq:3-management |
Message bus for async processing |
5672, 15672
|
smtp4dev |
rnwood/smtp4dev:v3 |
Fake SMTP server that captures emails |
9000, 2525
|
ββββββββββββββββ
Browser β Front :8080 β
ββββββββ¬ββββββββ
β HTTP
ββββββββΌββββββββ ββββββββββββββββ
β API :5000 βββββββΊβ SQL Server β (TemplateEditor + HangfireDb)
ββββ¬βββββββββ¬βββ ββββββββββββββββ
β β
AMQP β β SMTP
βββββββββββββββΌβββ ββββΌβββββββββββ
β RabbitMQ β β smtp4dev β β email UI on :9000
ββββββββββββββββββ βββββββββββββββ
It's a classic .NET architecture: Hangfire for scheduled jobs, RabbitMQ for the message queue, and smtp4dev to intercept every email without ever sending anything outside. Perfect for development.
β οΈ Before you start: no encryption locally
In production, TemplateMaster encrypts sensitive data in the database. Locally, this encryption is disabled to make debugging easier. Bottom line: never use this environment with real or sensitive data.
π¦ Prerequisites
- Docker Desktop (Docker Compose is included)
- These ports must be free:
5000,8080,1433,5672,15672,9000(and2525for SMTP)
A handy way to check whether a port is already in use:
# Linux / macOS
lsof -i :1433
# Windows (PowerShell)
netstat -ano | findstr :1433
π‘ Already running SQL Server on your machine? It's probably holding port 1433. Just change the mapping to
"14330:1433".
ποΈ Project structure
Two files are all you need:
/
ββ docker-compose.yaml
ββ .env
docker-compose.yaml
version: "3"
services:
smtp4dev:
image: rnwood/smtp4dev:v3
container_name: smtp4dev
restart: always
ports:
- "9000:80"
- "2525:25"
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest
container_name: mssql
environment:
SA_PASSWORD: "YourStrong!Passw0rd"
ACCEPT_EULA: "Y"
ports:
- "1433:1433"
rabbitmq-service:
image: rabbitmq:3-management
container_name: rabbitmq_service
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: "${RABBIT_MQ_USER_NAME}"
RABBITMQ_DEFAULT_PASS: "${RABBIT_MQ_USER_PASSWORD}"
editorTemplate-api:
image: templatemaster/editortemplate-api:latest
container_name: editorTemplate-api
ports:
- "5000:8080"
volumes:
- "C:/EditorTemplate:/app/EditeurDoc"
#- /home/EditorTemplate:/app/EditeurDoc
environment:
ASPNETCORE_ENVIRONMENT: Development
ConnectionStrings__EditeurBdd: "${EDITEUR_BDD}"
ConnectionStrings__HangfireDb: "${HANGFIRE_BDD}"
EmailSettings__SMTPSetting__Host: "smtp4dev"
EmailSettings__Port: "${EDITEUR_EMAIL_PORT}"
EmailSettings__UserName: "${EDITEUR_EMAIL_USER_NAME}"
EmailSettings__Password: "${EDITEUR_EMAIL_PASSWORD}"
RabbitMQ__HostName: "rabbitmq://rabbitmq-service"
RabbitMQ__UserName: "${RABBIT_MQ_USER_NAME}"
RabbitMQ__Password: "${RABBIT_MQ_USER_PASSWORD}"
contactEmail: "${CONTACT_EMAIL}"
Authentication__Issuer: "${ATHENTICATION_ISSUER}"
Authentication__Audience: "${ATHENTICATION_AUDIENCE}"
Authentication__ClientSecret: "${ATHENTICATION_CLIENT_SECRET}"
BaseUrl: "${BASE_URL_API}"
BaseUrlFront: "${BASE_URL_FRONT}"
adminEmail: "${ADMIN_EMAIL}"
adminPassword: "${ADMIN_PASSWORD}"
LICENSE_KEY: "${LICENSE_KEY}"
depends_on:
- rabbitmq-service
- smtp4dev
- mssql
editorTemplate-front:
image: templatemaster/editortemplate-front:latest
container_name: editortemplate-front
ports:
- "8080:80"
environment:
ASPNETCORE_ENVIRONMENT: Development
BASE_URL: "${BASE_URL_API}"
A few things worth noting:
-
Variables like
ConnectionStrings__EditeurBddorRabbitMQ__HostNamefollow the ASP.NET Core convention: the double underscore__maps to the:separator inappsettings.json. SoRabbitMQ__HostNameoverridesRabbitMQ:HostName. -
Services talk to each other by name on the Docker network: the API reaches the database via
Server=mssql, SMTP viasmtp4dev(internal port25) and RabbitMQ viarabbitmq-service. -
The volume
C:/EditorTemplate:/app/EditeurDocpersists generated files on the host. On Linux/macOS, uncomment the/home/EditorTemplate:/app/EditeurDocline (and comment out the Windows one).
.env
EDITEUR_BDD='Server=mssql;Database=TemplateEditor;Trusted_Connection=False;TrustServerCertificate=True;User id=sa;Password=YourStrong!Passw0rd;'
HANGFIRE_BDD='Server=mssql;Database=HangfireDb;Trusted_Connection=False;TrustServerCertificate=True;User id=sa;Password=YourStrong!Passw0rd;'
EDITEUR_EMAIL_HOST='smtp4dev'
EDITEUR_EMAIL_PORT=25
EDITEUR_EMAIL_USER_NAME='contact@templatemaster.fr'
EDITEUR_EMAIL_PASSWORD=''
BASE_URL_API='http://localhost:5000/api/v1'
BASE_URL_FRONT='http://localhost:8080/'
RABBIT_MQ_USER_NAME='editorTemplate'
RABBIT_MQ_USER_PASSWORD='editorTemplatePassword'
ADMIN_EMAIL='admin@templatemaster.fr'
ADMIN_PASSWORD='admin'
CONTACT_EMAIL='admin@gmail.com'
ATHENTICATION_ISSUER='templatemaster.fr'
ATHENTICATION_AUDIENCE='account'
ATHENTICATION_CLIENT_SECRET='<generate-your-own-secret>'
LICENSE_KEY=''
β οΈ Watch the spelling: the variables really are named
ATHENTICATION_*(no "U"). Don't "fix" them in.envwithout also fixingdocker-compose.yaml, or the API will start with empty values.
For the authentication secret, rather than copying a value found online, generate your own:
openssl rand -hex 32
π Start it up
Put both files in the same folder, then run:
docker compose up -d
docker compose(with a space) is the Compose v2 syntax bundled with Docker Desktop. The legacydocker-composecommand works too.
Once the containers are up:
| URL | What |
|---|---|
| http://localhost:8080/signin | π TemplateMaster UI |
| http://localhost:9000 | π¬ smtp4dev β every email sent by the app |
| http://localhost:15672 | π¨ RabbitMQ management console (credentials from .env) |
| https://webhook.site | π To test your outgoing webhooks |
π First login
On first startup, an admin account is created automatically from ADMIN_EMAIL and ADMIN_PASSWORD. With the .env above:
Email: admin@templatemaster.fr
Password: admin
Head over to http://localhost:8080/signin⦠and start building your first template! Send a test email: it will show up instantly in smtp4dev.
π οΈ Everyday commands
# Follow the API logs
docker compose logs -f editorTemplate-api
# Check that .env variables are interpolated correctly
docker compose config
# Stop the stack
docker compose down
# Update the TemplateMaster images (:latest tag)
docker compose pull && docker compose up -d
π‘ The official docs mention
docker compose up -d --build. Since every service uses a prebuilt image (nobuild:section),--buildhas no effect here. To get a new version, rundocker compose pull.
π§ Going further: making the stack more robust
The official configuration works, but it has a classic pitfall: depends_on waits for the SQL Server container to start, not for it to be *ready. SQL Server often takes 10β30 seconds before accepting connections, and the API can crash on startup. The docs actually hint at this: *make sure the database is initialized before the API starts.
The fix: add healthchecks and make the API wait for them.
services:
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
MSSQL_SA_PASSWORD: "${SA_PASSWORD}"
ACCEPT_EULA: "Y"
ports:
- "1433:1433"
volumes:
- mssql-data:/var/opt/mssql
healthcheck:
test: >
/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P "$${MSSQL_SA_PASSWORD}" -Q "SELECT 1"
|| /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$${MSSQL_SA_PASSWORD}" -Q "SELECT 1"
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
rabbitmq-service:
image: rabbitmq:3-management
# ...
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 5
editorTemplate-api:
# ...
restart: unless-stopped
depends_on:
mssql:
condition: service_healthy
rabbitmq-service:
condition: service_healthy
smtp4dev:
condition: service_started
volumes:
mssql-data:
What changes:
- β Healthchecks on SQL Server and RabbitMQ β the API only starts once its dependencies actually respond.
- β
Named volume
mssql-dataβ your templates survive adocker compose down(without-v). - β
SA password in
.env(SA_PASSWORD=...) instead of hardcoded in the compose file. Remember to update it inEDITEUR_BDDandHANGFIRE_BDDtoo. - β
MSSQL_SA_PASSWORDis the variable name Microsoft currently recommends (SA_PASSWORDis deprecated). - β
The
version: "3"line can be removed: Compose v2 ignores it.
The double
sqlcmdin the healthcheck covers both possible tool locations depending on the image version (mssql-tools18on recent images,mssql-toolson older ones).
π©Ί Quick troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Bind for 0.0.0.0:1433 failed: port is already allocated |
A local SQL Server is already running | Change the host port (14330:1433) |
| The API keeps restarting on first launch | SQL Server isn't ready yet | Healthcheck + condition: service_healthy (see above) |
The mssql container exits immediately |
SA password too weak | 8+ chars with upper/lowercase, digits and symbols |
| Empty variables in the API | Typo / .env not in the right folder |
docker compose config to see resolved values |
| No email received | That's expected! They're captured | Open http://localhost:9000 |
| Volume error on Linux/macOS | Windows C:/... path |
Use /home/EditorTemplate:/app/EditeurDoc
|
π― Wrapping up
With two files and one command, you get a complete environment to try TemplateMaster: visual editor, PDF/email generation API, SQL Server database, RabbitMQ bus and email capture. Great for prototyping templates or validating an integration (for example via the .NET NuGet package) before going to production.
Security reminder: this environment is for development only β no database encryption, default credentials (admin/admin), well-known SA password. Never expose it to the Internet.
π Full documentation: templatemaster.fr/fr/documentation
Have you tried it? Tell me in the comments what kind of templates you're generating! π
Top comments (0)