Sign inSign up

mactorient/mjml-server

By mactorient

Updated 9 months ago

MJML to HTML rendering API server using Express.js, ready for Docker & production use.

Image
Languages & frameworks
Developer tools
Web servers
2

1.1K

mactorient/mjml-server repository overview

MJML HTTP Server

Docker Pulls Docker Image Size Docker Stars License

Self-hosted MJML rendering API - Convert MJML to responsive HTML emails via HTTP.

🚀 Quick Start

docker run -p 15500:15500 mactorient/mjml-server:latest

Test it:

curl -X POST http://localhost:15500/v1/render \
  -H "Content-Type: application/json" \
  -d '{"mjml": "<mjml><mj-body><mj-section><mj-column><mj-text>Hello World!</mj-text></mj-column></mj-section></mj-body></mjml>"}'

✨ Features

  • 🎨 Template Variables - Mustache syntax support ({{name}}, {{email}})
  • 📦 Batch Rendering - Process up to 50 emails per request
  • ✅ Validation Endpoint - Validate MJML without rendering
  • 🛡️ Security - Optional HTTP Basic Auth & Rate Limiting
  • ⚡ Performance - Gzip compression, optimized bundle (~241MB image)
  • 📊 Monitoring - Health checks, request logging, metrics-ready
  • 🔧 Configurable - 13+ options via ENV vars or CLI flags

🐳 Image Details

Size: ~241MB (Alpine-based, esbuild bundled)
Architectures: linux/amd64, linux/arm64
Base: node:24-alpine

What's Included
  • ✅ Non-root user (nodejs:1001)
  • ✅ dumb-init for proper signal handling
  • ✅ Health checks built-in
  • ✅ Source maps for debugging
  • ✅ Production-optimized bundle

📝 API Endpoints

EndpointMethodDescription
/v1/renderPOSTRender MJML to HTML
/v1/render/batchPOSTBatch render (max 50)
/v1/validatePOSTValidate MJML syntax
/v1/healthGETHealth check

🔧 Configuration

Environment Variables
services:
  mjml-server:
    image: mactorient/mjml-server:latest
    ports:
      - '15500:15500'
    environment:
      # MJML Options
      MJML_MINIFY: 'true'
      MJML_KEEP_COMMENTS: 'false'
      MJML_VALIDATION_LEVEL: 'strict'

      # Server Options
      MJML_HOST: '0.0.0.0'
      MJML_PORT: '15500'
      MJML_USE_COMPRESSION: 'true'

      # Security (optional)
      MJML_AUTH_USER: 'admin'
      MJML_AUTH_PASS: 'secret'
      MJML_ENABLE_RATE_LIMIT: 'true'
      MJML_RATE_LIMIT_MAX: '100'
      MJML_RATE_LIMIT_WINDOW: '15'
Available Options
VariableDefaultDescription
MJML_HOST0.0.0.0Server host
MJML_PORT15500Server port
MJML_MINIFYfalseMinify HTML output
MJML_KEEP_COMMENTStrueKeep MJML comments
MJML_VALIDATION_LEVELsoftstrict/soft/skip
MJML_USE_COMPRESSIONtrueGzip compression
MJML_MAX_BODY1mbMax request size
MJML_AUTH_USER-HTTP Basic Auth user
MJML_AUTH_PASS-HTTP Basic Auth pass
MJML_ENABLE_RATE_LIMITfalseEnable rate limiting
MJML_RATE_LIMIT_MAX100Max requests per window
MJML_RATE_LIMIT_WINDOW15Window in minutes

💡 Usage Examples

Basic Rendering
curl -X POST http://localhost:15500/v1/render \
  -H "Content-Type: application/json" \
  -d '{
    "mjml": "<mjml><mj-body>...</mj-body></mjml>"
  }'
With Template Variables
curl -X POST http://localhost:15500/v1/render \
  -H "Content-Type: application/json" \
  -d '{
    "mjml": "<mjml><mj-body><mj-text>Hello {{name}}!</mj-text></mj-body></mjml>",
    "variables": {
      "name": "John Doe",
      "email": "[email protected]"
    }
  }'
Batch Rendering
curl -X POST http://localhost:15500/v1/render/batch \
  -H "Content-Type: application/json" \
  -d '{
    "requests": [
      {
        "mjml": "<mjml>...</mjml>",
        "variables": {"name": "Alice"}
      },
      {
        "mjml": "<mjml>...</mjml>",
        "variables": {"name": "Bob"}
      }
    ]
  }'
Validation Only
curl -X POST http://localhost:15500/v1/validate \
  -H "Content-Type: application/json" \
  -d '{
    "mjml": "<mjml><mj-body>...</mj-body></mjml>"
  }'

🏃 Running with Docker Compose

version: '3.8'

services:
  mjml-server:
    image: mactorient/mjml-server:latest
    restart: unless-stopped
    ports:
      - '15500:15500'
    environment:
      MJML_MINIFY: 'true'
      MJML_ENABLE_RATE_LIMIT: 'true'
      MJML_RATE_LIMIT_MAX: '200'
    healthcheck:
      test: ['CMD', 'wget', '--quiet', '--tries=1', '--spider', 'http://localhost:15500/v1/health']
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

🔒 Security Best Practices

services:
  mjml-server:
    image: mactorient/mjml-server:latest
    ports:
      - '15500:15500'
    environment:
      # Enable authentication
      MJML_AUTH_USER: '${MJML_USER}'
      MJML_AUTH_PASS: '${MJML_PASS}'

      # Enable rate limiting
      MJML_ENABLE_RATE_LIMIT: 'true'
      MJML_RATE_LIMIT_MAX: '100'

      # Strict validation
      MJML_VALIDATION_LEVEL: 'strict'

    # Resource limits
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

📊 Monitoring & Health

# Check health
curl http://localhost:15500/v1/health

# Response
{
  "status": "ok",
  "message": "MJML Render API is healthy",
  "mjml_version": "^4.16.1",
  "options": {
    "keepComments": true,
    "beautify": false,
    "minify": false,
    "validationLevel": "soft"
  }
}

🏗️ Build Info

  • Base Image: node:24-alpine
  • Build Tool: esbuild (bundled & minified)
  • Size: ~241MB (269MB → 241MB with optimization)
  • Startup: Fast (single bundled file)
  • Signal Handling: dumb-init for graceful shutdown

🐛 Troubleshooting

Port already in use?

docker run -p 8080:15500 mactorient/mjml-server:latest

Need more memory?

docker run -m 512m mactorient/mjml-server:latest

Check logs:

docker logs <container-id>

📜 License

MIT License - see LICENSE


For information, visit https://github.com/mertemr/mjml-server#readme

Tag summary

Content type

Image

Digest

sha256:5b3cdedb2

Size

58.3 MB

Last updated

9 months ago

docker pull mactorient/mjml-server