Deployment guide

Mailpit: self-hosted test SMTP server on your VPS

Deploy on a VPS Cloud →

Tutorial

Self-hosting

Mailpit: self-hosted test SMTP server on your VPS

Mailpit has replaced MailHog as the reference self-hosted test SMTP server: single binary, modern interface, MailHog-compatible API, and less than 50 MB of RAM at rest. This guide covers the full deployment on a VPS — Docker Compose, Nginx reverse proxy, CI/CD integration and network access hardening.

8 min read9 steps
Contents· Why replace MailHog with Mailpit1/11
  1. 01Why replace MailHog with Mailpit
  2. 02What Mailpit delivers in practice
  3. 03Prerequisites
  4. 04Deploy Mailpit in 5 steps
  5. 05Mailpit in CI/CD: testing emails in the pipeline
  6. 06CI/CD integration by framework
  7. 07Securing access to Mailpit
  8. 08Tip: clear the mailbox between tests
  9. 09Troubleshooting — 4 common cases
  10. 10Mailpit vs MailHog vs Mailtrap vs Inbucket vs smtp4dev
  11. 11Advanced use cases

Why replace MailHog with Mailpit

MailHog has been unmaintained since late 2022. It accumulates outdated dependencies, its interface dates back to Bootstrap's early years, and its API has never evolved. Mailpit addresses each of these points: it is actively developed (latest release in 2024), offers a real-time reactive UI, and exposes a REST API that remains backward compatible with MailHog's to avoid breaking existing scripts.

The switch requires no data migration: Mailpit stores nothing by default. Every restart begins with an empty mailbox, which is exactly the intended behavior in a test pipeline.

What Mailpit delivers in practice

  • Modern interface with full-text search, HTML/text rendering, mobile preview and header inspection
  • Single statically compiled Go binary — no system dependencies, runs in a 20 MB container
  • Memory footprint below 50 MB at rest, stable even with thousands of messages in a session
  • REST API backward compatible with MailHog (/api/v1/messages, /api/v1/search) for existing scripts
  • Authenticated SMTP support, optional TLS, and network-based access control
  • Configurable webhook to notify an external endpoint on each received email

Prerequisites

A VPS running Debian 12 or Ubuntu 22.04/24.04 with Docker and Docker Compose v2 installed is sufficient. Mailpit is among the lightest tools in the catalog: plan for 1 shared vCPU and 256 MB of RAM for developer or CI use. A dedicated subdomain (mail.your-domain.com) is recommended to access the interface from a browser over HTTPS — otherwise access is only possible locally via an SSH tunnel.

Ports used: 1025 (SMTP) and 8025 (web interface). If a mail service is already running on port 1025, you will need to shift Mailpit (see the troubleshooting section).

Deploy Mailpit in 5 steps

  1. Create the directory and Compose file

    mkdir -p /opt/mailpit && cd /opt/mailpit

    Create the following docker-compose.yml file:

    services:
      mailpit:
        image: axllent/mailpit:latest
        container_name: mailpit
        restart: unless-stopped
        ports:
          - "127.0.0.1:1025:1025"
          - "127.0.0.1:8025:8025"
        environment:
          MP_MAX_MESSAGES: 500
          MP_SMTP_AUTH_ACCEPT_ANY: "true"
          MP_SMTP_AUTH_ALLOW_INSECURE: "true"
          MP_UI_AUTH: "admin:changeme"
        volumes:
          - mailpit_data:/data
    volumes:
      mailpit_data:

    Ports are bound to 127.0.0.1 only: Mailpit is never exposed directly on the public IP. The MP_UI_AUTH variable enables HTTP basic authentication on the web interface — change the password. MP_MAX_MESSAGES limits the number of emails kept in memory per session.

  2. Start the container

    docker compose up -d
    docker compose logs -f mailpit

    The line starting on [::]:1025 confirms the SMTP server is listening. Check that the ports are correctly bound to localhost:

    ss -tlnp | grep -E '1025|8025'

    Expected: 127.0.0.1:1025 and 127.0.0.1:8025 only — never 0.0.0.0.

  3. Configure the Nginx reverse proxy

    Create /etc/nginx/sites-available/mailpit:

    server {
        listen 443 ssl;
        server_name mail.your-domain.com;
    
        ssl_certificate     /etc/letsencrypt/live/mail.your-domain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/mail.your-domain.com/privkey.pem;
    
        location / {
            proxy_pass         http://127.0.0.1:8025;
            proxy_http_version 1.1;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection "upgrade";
            proxy_set_header   Host $host;
            proxy_read_timeout 86400;
        }
    }

    The proxy_read_timeout 86400 is required for WebSocket connections used by the real-time interface. Enable the vhost and reload Nginx:

    ln -s /etc/nginx/sites-available/mailpit /etc/nginx/sites-enabled/
    nginx -t && systemctl reload nginx

    Obtain a Let's Encrypt certificate if not already done:

    certbot --nginx -d mail.your-domain.com
  4. Test sending with curl

    Verify the SMTP server receives a message:

    curl --url 'smtp://127.0.0.1:1025' \
      --mail-from '[email protected]' \
      --mail-rcpt '[email protected]' \
      --upload-file - <<EOF
    From: [email protected]
    To: [email protected]
    Subject: Mailpit Test
    
    Test message body.
    EOF

    Then open https://mail.your-domain.com in your browser. The message should appear in the interface. If you configured MP_UI_AUTH, enter the credentials at the HTTP basic auth prompt.

  5. Configure your application to point to Mailpit

    In your .env file (Laravel, Symfony, n8n, Django…), replace the SMTP configuration with:

    MAIL_HOST=127.0.0.1
    MAIL_PORT=1025
    MAIL_ENCRYPTION=null
    MAIL_USERNAME=null
    MAIL_PASSWORD=null

    For applications running in Docker on the same host, use the Docker network name or the gateway IP (172.17.0.1 by default) instead of 127.0.0.1.

Mailpit in CI/CD: testing emails in the pipeline

In CI environments, Mailpit integrates as a sidecar service. It starts in seconds and requires no external configuration.

CI/CD integration by framework

  1. GitHub Actions — Mailpit service

    services:
      mailpit:
        image: axllent/mailpit:latest
        ports:
          - 1025:1025
          - 8025:8025

    In your job environment variables, set MAIL_HOST=127.0.0.1 and MAIL_PORT=1025. Mailpit accepts any email without authentication by default — no additional SMTP configuration is needed.

  2. Laravel — PHPUnit

    In phpunit.xml, replace the transport with SMTP:

    <env name="MAIL_MAILER" value="smtp"/>
    <env name="MAIL_HOST" value="127.0.0.1"/>
    <env name="MAIL_PORT" value="1025"/>

    In your test, send the mail then query the Mailpit API to verify receipt:

    $response = Http::get('http://127.0.0.1:8025/api/v1/messages');
    $this->assertSame($response->json('total'), 1);
    $this->assertStringContainsString(
        'Welcome',
        $response->json('messages.0.Subject')
    );
  3. Django — pytest

    In settings.py (or settings_test.py):

    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    EMAIL_HOST = '127.0.0.1'
    EMAIL_PORT = 1025

    In your pytest test, verify via the Mailpit API:

    import requests
    
    def test_welcome_email_sent():
        # trigger the send in your view/service
        r = requests.get('http://127.0.0.1:8025/api/v1/messages')
        assert r.json()['total'] == 1
        assert 'Welcome' in r.json()['messages'][0]['Subject']
  4. n8n — workflow testing

    In n8n's Send Email node, configure the SMTP server to 127.0.0.1:1025 without authentication. To automatically verify that a workflow triggered a send, call GET http://127.0.0.1:8025/api/v1/messages from an HTTP Request node and validate the total in an IF node.

Securing access to Mailpit

Mailpit stores emails in memory only — no files on disk, no database. This is an advantage for confidentiality (no persistent traces), but also a limitation: a restart empties the mailbox without warning.

Network restriction. Ports 1025 and 8025 are bound to 127.0.0.1 in the Docker Compose configuration above. Never expose them on 0.0.0.0 in production or on a shared server: anyone could relay emails or read intercepted messages.

HTTP basic authentication on the interface. The MP_UI_AUTH=user:password variable enables basic authentication on the web interface. Use a strong password — the interface exposes the full content of your test emails, often populated with confirmation links, tokens and temporary passwords.

Authenticated SMTP. For multi-team environments, enable MP_SMTP_AUTH with an account file rather than MP_SMTP_AUTH_ACCEPT_ANY. This prevents a misconfigured service from sending emails to another project's test mailbox.

Tip: clear the mailbox between tests

The Mailpit API exposes a purge endpoint: DELETE http://127.0.0.1:8025/api/v1/messages. Call it in your PHPUnit setUp() or pytest fixture to start each test with an empty mailbox, without having to restart the container.

Troubleshooting — 4 common cases

Port 1025 already in use. A local Postfix or Sendmail server may be listening on this port. Check with ss -tlnp | grep 1025. If so, change the host port in docker-compose.yml (e.g. 1026:1025) and update MAIL_PORT in your applications.

Access denied from a CI container. Docker containers in GitHub Actions can access the runner's 127.0.0.1, but not other services via localhost if the network is not properly shared. Use the runner's host network or reference the service by its name (mailpit:1025) in the workflow file.

Interface inaccessible behind reverse proxy. The real-time interface WebSocket connections can be cut by an aggressive Nginx timeout. Check that proxy_read_timeout is set to 86400 and that the Upgrade and Connection headers are forwarded.

Performance with thousands of emails. Mailpit keeps everything in memory. If your pipeline generates hundreds of emails per run, set MP_MAX_MESSAGES to a reasonable value (100–500) to prevent container memory from growing unbounded over long sessions.

Mailpit vs MailHog vs Mailtrap vs Inbucket vs smtp4dev

Scroll the table

ToolMaintenanceRAM at restREST APIPersistenceSMTP auth
**Mailpit**Active (2024)< 50 MBYes (v1 + v2)Memory onlyOptional
MailHogAbandoned (2022)~30 MBYes (v1 only)Memory onlyNo
Mailtrap (SaaS)ActiveN/A (cloud)YesCloud (30 days)Yes (cloud)
InbucketActive~20 MBYesMemory or diskNo
smtp4devActive~80 MB (.NET)YesOptional SQLiteNo

Advanced use cases

Multi-project interception. A single Mailpit instance can receive emails from multiple projects simultaneously. Use full-text search or filter by recipient in the interface to isolate messages from each application.

Webhook to Slack or n8n. The MP_WEBHOOK_URL variable triggers a JSON POST on each receipt. Connect it to an n8n workflow to route certain test emails to a team Slack channel — useful for shared integration tests.

Export and replay. The API exposes emails in EML format (GET /api/v1/message/<id>.eml). Download an intercepted message to replay it in a real mail client or to keep it as a test fixture.

Mailpit in a complete development stack. Add Mailpit as a service in your main project's docker-compose.yml. It integrates naturally with Laravel Sail, Symfony Docker or self-hosted n8n stacks — a single docker compose up starts the entire development environment with SMTP interception included.

Deploy Mailpit in one click on ServOrbit

With a ServOrbit Cloud VPS, Mailpit is available directly from the marketplace. Set up your SMTP test environment in two minutes, with no separate server to manage.