Files
2025-11-30 09:05:59 +08:00

29 KiB

MailHog Integration Patterns

Framework Integration Guides

Node.js Integration

Nodemailer Configuration

const nodemailer = require('nodemailer');

// MailHog transporter configuration
const transporter = nodemailer.createTransporter({
    host: 'localhost',
    port: 1025,
    secure: false, // TLS not required for MailHog
    auth: false,   // No authentication required
    tls: {
        rejectUnauthorized: false
    }
});

// Send email function
async function sendEmail(options) {
    try {
        const info = await transporter.sendMail({
            from: options.from || 'test@sender.local',
            to: options.to,
            subject: options.subject,
            text: options.text,
            html: options.html
        });

        console.log('Email sent successfully:', info.messageId);
        return info;
    } catch (error) {
        console.error('Email sending failed:', error);
        throw error;
    }
}

// Usage examples
await sendEmail({
    to: 'recipient@example.com',
    subject: 'Test Email',
    text: 'This is a test email sent via MailHog',
    html: '<p>This is a <strong>test email</strong> sent via MailHog</p>'
});

Express.js Integration

const express = require('express');
const nodemailer = require('nodemailer');

const app = express();
app.use(express.json());

// Configure transporter for different environments
const getTransporter = () => {
    if (process.env.NODE_ENV === 'production') {
        // Production SMTP configuration
        return nodemailer.createTransporter({
            host: process.env.SMTP_HOST,
            port: process.env.SMTP_PORT,
            secure: true,
            auth: {
                user: process.env.SMTP_USER,
                pass: process.env.SMTP_PASS
            }
        });
    } else {
        // Development with MailHog
        return nodemailer.createTransporter({
            host: 'localhost',
            port: 1025,
            auth: false
        });
    }
};

// Email endpoint
app.post('/send-email', async (req, res) => {
    try {
        const transporter = getTransporter();
        const { to, subject, text, html } = req.body;

        await transporter.sendMail({
            from: process.env.EMAIL_FROM || 'noreply@localhost',
            to,
            subject,
            text,
            html
        });

        res.json({ success: true, message: 'Email sent successfully' });
    } catch (error) {
        console.error('Email error:', error);
        res.status(500).json({ success: false, error: error.message });
    }
});

// Test endpoint for development
app.get('/test-email', async (req, res) => {
    if (process.env.NODE_ENV === 'production') {
        return res.status(404).json({ error: 'Not available in production' });
    }

    try {
        const transporter = getTransporter();
        await transporter.sendMail({
            to: 'test@recipient.local',
            subject: 'Test Email from Express',
            text: 'This is a test email from the Express application',
            html: '<h1>Test Email</h1><p>This email was sent from Express using MailHog.</p>'
        });

        res.json({ success: true, message: 'Test email sent to MailHog' });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
    console.log('Environment:', process.env.NODE_ENV || 'development');
});

Jest Testing Integration

// emailService.test.js
const nodemailer = require('nodemailer');
const MailHogClient = require('./mailhog-client');

describe('Email Service Integration', () => {
    const mailhog = new MailHogClient();
    const transporter = nodemailer.createTransporter({
        host: 'localhost',
        port: 1025,
        auth: false
    });

    beforeEach(async () => {
        // Clear messages before each test
        await mailhog.deleteMessages('all');
    });

    afterEach(async () => {
        // Clean up after each test
        await mailhog.deleteMessages('all');
    });

    describe('Basic Email Sending', () => {
        test('should send email to MailHog', async () => {
            const emailData = {
                from: 'sender@test.local',
                to: 'recipient@test.local',
                subject: 'Test Subject',
                text: 'Test body content'
            };

            // Send email
            const result = await transporter.sendMail(emailData);
            expect(result.messageId).toBeDefined();

            // Wait a moment for MailHog to process
            await new Promise(resolve => setTimeout(resolve, 500));

            // Verify in MailHog
            const messages = await mailhog.searchMessages('to:recipient@test.local');
            expect(messages.total).toBe(1);

            const message = messages.items[0];
            expect(message.Subject).toBe('Test Subject');
            expect(message.From).toBe('sender@test.local');
        });

        test('should send HTML email', async () => {
            const htmlContent = '<h1>HTML Email</h1><p>This is <strong>HTML content</strong></p>';

            await transporter.sendMail({
                from: 'test@sender.local',
                to: 'html@test.local',
                subject: 'HTML Test',
                html: htmlContent
            });

            await new Promise(resolve => setTimeout(resolve, 500));

            const messages = await mailhog.getMessages();
            expect(messages.total).toBe(1);

            const message = messages.items[0];
            expect(message.Content.Body).toContain('<h1>HTML Email</h1>');
        });
    });

    describe('Bulk Email Operations', () => {
        test('should handle multiple recipients', async () => {
            const recipients = ['user1@test.local', 'user2@test.local', 'user3@test.local'];

            await transporter.sendMail({
                from: 'bulk@sender.local',
                to: recipients.join(', '),
                subject: 'Bulk Email Test',
                text: 'This email goes to multiple recipients'
            });

            await new Promise(resolve => setTimeout(resolve, 1000));

            const messages = await mailhog.getMessages();
            expect(messages.total).toBe(1);

            const message = messages.items[0];
            const recipientAddresses = message.To.map(to => to.Address);
            recipients.forEach(recipient => {
                expect(recipientAddresses).toContain(recipient);
            });
        });
    });
});

Python Integration

Flask Email Integration

from flask import Flask, request, jsonify
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os

app = Flask(__name__)

class EmailService:
    def __init__(self):
        self.config = self._get_config()

    def _get_config(self):
        if os.getenv('FLASK_ENV') == 'production':
            return {
                'host': os.getenv('SMTP_HOST'),
                'port': int(os.getenv('SMTP_PORT', 587)),
                'use_tls': True,
                'username': os.getenv('SMTP_USER'),
                'password': os.getenv('SMTP_PASS')
            }
        else:
            # Development with MailHog
            return {
                'host': 'localhost',
                'port': 1025,
                'use_tls': False,
                'username': None,
                'password': None
            }

    def send_email(self, to_email, subject, text_body=None, html_body=None):
        msg = MIMEMultipart('alternative')
        msg['Subject'] = subject
        msg['From'] = os.getenv('EMAIL_FROM', 'noreply@localhost')
        msg['To'] = to_email

        if text_body:
            msg.attach(MIMEText(text_body, 'plain'))
        if html_body:
            msg.attach(MIMEText(html_body, 'html'))

        try:
            with smtplib.SMTP(self.config['host'], self.config['port']) as server:
                if self.config['use_tls']:
                    server.starttls()
                if self.config['username']:
                    server.login(self.config['username'], self.config['password'])

                server.send_message(msg)
                return True
        except Exception as e:
            print(f"Email sending failed: {e}")
            return False

email_service = EmailService()

@app.route('/send-email', methods=['POST'])
def send_email():
    data = request.get_json()
    success = email_service.send_email(
        to_email=data['to'],
        subject=data['subject'],
        text_body=data.get('text'),
        html_body=data.get('html')
    )

    return jsonify({'success': success})

@app.route('/test-email', methods=['GET'])
def test_email():
    if os.getenv('FLASK_ENV') == 'production':
        return jsonify({'error': 'Not available in production'}), 404

    success = email_service.send_email(
        to_email='test@recipient.local',
        subject='Test Email from Flask',
        text_body='This is a test email',
        html_body='<h1>Test Email</h1><p>This email was sent from Flask using MailHog.</p>'
    )

    return jsonify({'success': success})

if __name__ == '__main__':
    app.run(debug=True)

Django Integration

# settings.py
import os

if os.getenv('DJANGO_ENV') == 'production':
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    EMAIL_HOST = os.getenv('SMTP_HOST')
    EMAIL_PORT = int(os.getenv('SMTP_PORT', 587))
    EMAIL_USE_TLS = True
    EMAIL_HOST_USER = os.getenv('SMTP_USER')
    EMAIL_HOST_PASSWORD = os.getenv('SMTP_PASS')
else:
    # Development with MailHog
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    EMAIL_HOST = 'localhost'
    EMAIL_PORT = 1025
    EMAIL_USE_TLS = False
    EMAIL_HOST_USER = None
    EMAIL_HOST_PASSWORD = None

DEFAULT_FROM_EMAIL = os.getenv('EMAIL_FROM', 'noreply@localhost')

# views.py
from django.core.mail import send_mail, send_mass_mail
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.conf import settings
import requests

@require_http_methods(["POST"])
def send_email_view(request):
    data = request.json()

    try:
        send_mail(
            subject=data['subject'],
            message=data.get('message', ''),
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[data['to']],
            html_message=data.get('html_message'),
            fail_silently=False
        )
        return JsonResponse({'success': True})
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)})

@require_http_methods(["GET"])
def test_email_view(request):
    if os.getenv('DJANGO_ENV') == 'production':
        return JsonResponse({'error': 'Not available in production'}, status=404)

    try:
        send_mail(
            subject='Test Email from Django',
            message='This is a test email from Django',
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=['test@recipient.local'],
            html_message='<h1>Test Email</h1><p>Sent from Django using MailHog</p>',
            fail_silently=False
        )
        return JsonResponse({'success': True})
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)})

def check_mailhog_messages(request):
    """Utility endpoint to check MailHog messages during testing"""
    if os.getenv('DJANGO_ENV') == 'production':
        return JsonResponse({'error': 'Not available in production'}, status=404)

    try:
        response = requests.get('http://localhost:8025/api/v1/messages')
        messages = response.json()
        return JsonResponse({
            'total': messages.get('total', 0),
            'messages': [
                {
                    'id': msg['ID'],
                    'from': msg['From'],
                    'to': [to['Address'] for to in msg['To']],
                    'subject': msg['Subject']
                }
                for msg in messages.get('items', [])
            ]
        })
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

Pytest Integration

# test_email_integration.py
import pytest
import requests
import time
import smtplib
from email.mime.text import MIMEText

class MailHogClient:
    def __init__(self, base_url='http://localhost:8025'):
        self.base_url = base_url
        self.api_url = f'{base_url}/api/v1'

    def get_messages(self):
        response = requests.get(f'{self.api_url}/messages')
        return response.json()

    def search_messages(self, query):
        response = requests.post(
            f'{self.api_url}/search',
            json={'query': query}
        )
        return response.json()

    def delete_all_messages(self):
        response = requests.delete(f'{self.api_url}/messages')
        return response.status_code == 200

@pytest.fixture
def mailhog():
    return MailHogClient()

@pytest.fixture
def smtp_transporter():
    def create_transporter():
        return smtplib.SMTP('localhost', 1025)
    return create_transporter

@pytest.fixture(autouse=True)
def cleanup_mailhog(mailhog):
    """Clean up MailHog messages before and after each test"""
    mailhog.delete_all_messages()
    yield
    mailhog.delete_all_messages()

class TestEmailIntegration:
    def test_send_simple_email(self, mailhog, smtp_transporter):
        """Test sending a simple text email"""
        server = smtp_transporter()

        msg = MIMEText('This is a test email')
        msg['Subject'] = 'Test Email'
        msg['From'] = 'sender@test.local'
        msg['To'] = 'recipient@test.local'

        server.send_message(msg)
        server.quit()

        # Wait for MailHog to process
        time.sleep(0.5)

        # Verify email was received
        messages = mailhog.search_messages('to:recipient@test.local')
        assert messages['total'] == 1

        message = messages['items'][0]
        assert message['Subject'] == 'Test Email'
        assert 'sender@test.local' in message['From']

    def test_send_html_email(self, mailhog, smtp_transporter):
        """Test sending HTML email"""
        server = smtp_transporter()

        html_content = '''
        <html>
            <body>
                <h1>HTML Email Test</h1>
                <p>This is a <strong>formatted</strong> email.</p>
            </body>
        </html>
        '''

        msg = MIMEText(html_content, 'html')
        msg['Subject'] = 'HTML Email Test'
        msg['From'] = 'html@sender.local'
        msg['To'] = 'html@recipient.local'

        server.send_message(msg)
        server.quit()

        time.sleep(0.5)

        messages = mailhog.search_messages('subject:HTML Email Test')
        assert messages['total'] == 1

    def test_multiple_recipients(self, mailhog, smtp_transporter):
        """Test email with multiple recipients"""
        server = smtp_transporter()

        recipients = ['user1@test.local', 'user2@test.local', 'user3@test.local']

        msg = MIMEText('This email goes to multiple recipients')
        msg['Subject'] = 'Multiple Recipients Test'
        msg['From'] = 'bulk@sender.local'
        msg['To'] = ', '.join(recipients)

        server.send_message(msg)
        server.quit()

        time.sleep(0.5)

        messages = mailhog.get_messages()
        assert messages['total'] == 1

        message = messages['items'][0]
        recipient_addresses = [to['Address'] for to in message['To']]

        for recipient in recipients:
            assert recipient in recipient_addresses

PHP Integration

Laravel Configuration

// config/mail.php
return [
    'default' => env('MAIL_MAILER', 'smtp'),

    'mailers' => [
        'smtp' => [
            'transport' => 'smtp',
            'host' => env('MAIL_HOST', 'localhost'),
            'port' => env('MAIL_PORT', 1025),
            'encryption' => env('MAIL_ENCRYPTION', null),
            'username' => env('MAIL_USERNAME'),
            'password' => env('MAIL_PASSWORD'),
            'timeout' => null,
            'auth_mode' => null,
        ],
    ],

    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'noreply@localhost'),
        'name' => env('MAIL_FROM_NAME', 'Example App'),
    ],
];
// .env.example
# Development environment (MailHog)
MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_ENCRYPTION=null
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS=noreply@localhost
MAIL_FROM_NAME="${APP_NAME}"

# Production environment
# MAIL_MAILER=smtp
# MAIL_HOST=smtp.mailprovider.com
# MAIL_PORT=587
# MAIL_ENCRYPTION=tls
# MAIL_USERNAME=your-email@example.com
# MAIL_PASSWORD=your-password
# MAIL_FROM_ADDRESS=noreply@example.com
// app/Http/Controllers/EmailController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class EmailController extends Controller
{
    public function sendEmail(Request $request)
    {
        $validated = $request->validate([
            'to' => 'required|email',
            'subject' => 'required|string|max:255',
            'message' => 'required|string',
        ]);

        try {
            Mail::raw($validated['message'], function ($message) use ($validated) {
                $message->to($validated['to'])
                       ->subject($validated['subject']);
            });

            return response()->json(['success' => true]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'error' => $e->getMessage()
            ], 500);
        }
    }

    public function sendHtmlEmail(Request $request)
    {
        $validated = $request->validate([
            'to' => 'required|email',
            'subject' => 'required|string|max:255',
            'html' => 'required|string',
        ]);

        try {
            Mail::html($validated['html'], function ($message) use ($validated) {
                $message->to($validated['to'])
                       ->subject($validated['subject']);
            });

            return response()->json(['success' => true]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'error' => $e->getMessage()
            ], 500);
        }
    }

    public function testEmail()
    {
        if (app()->environment('production')) {
            return response()->json(['error' => 'Not available in production'], 404);
        }

        try {
            Mail::html('<h1>Test Email</h1><p>This is a test email from Laravel using MailHog.</p>', function ($message) {
                $message->to('test@recipient.local')
                       ->subject('Laravel Test Email');
            });

            return response()->json(['success' => true]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'error' => $e->getMessage()
            ], 500);
        }
    }
}

Ruby on Rails Integration

# config/environments/development.rb
Rails.application.configure do
  # ... other configurations ...

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address: 'localhost',
    port: 1025,
    authentication: 'plain',
    user_name: nil,
    password: nil,
    enable_starttls_auto: false
  }

  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end

# config/environments/production.rb
Rails.application.configure do
  # ... other configurations ...

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address: ENV.fetch('SMTP_HOST'),
    port: ENV.fetch('SMTP_PORT', 587).to_i,
    authentication: 'plain',
    user_name: ENV.fetch('SMTP_USER'),
    password: ENV.fetch('SMTP_PASS'),
    domain: ENV.fetch('SMTP_DOMAIN'),
    enable_starttls_auto: true
  }

  config.action_mailer.default_url_options = { host: ENV.fetch('APP_HOST') }
end
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end

# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def welcome_email(user)
    @user = user
    mail(to: @user.email, subject: 'Welcome to Our App!')
  end

  def test_email
    return false if Rails.env.production?

    mail(to: 'test@recipient.local', subject: 'Rails Test Email') do |format|
      format.text { render plain: 'This is a test email from Rails' }
      format.html { render html: '<h1>Test Email</h1><p>This email was sent from Rails using MailHog.</p>'.html_safe }
    end
  end
end

Testing Framework Integration

Cypress E2E Testing

// cypress/support/commands.js
Cypress.Commands.add('checkMailhogEmail', (options = {}) => {
    const { to, subject, timeout = 10000 } = options;

    const startTime = Date.now();

    const checkEmail = () => {
        return cy.request('http://localhost:8025/api/v1/messages')
            .then((response) => {
                const messages = response.body.items;

                let foundMessages = messages;

                if (to) {
                    foundMessages = foundMessages.filter(msg =>
                        msg.To.some(recipient => recipient.Address.includes(to))
                    );
                }

                if (subject) {
                    foundMessages = foundMessages.filter(msg =>
                        msg.Subject && msg.Subject.includes(subject)
                    );
                }

                if (foundMessages.length > 0) {
                    return foundMessages[0];
                }

                if (Date.now() - startTime > timeout) {
                    throw new Error(`Email not found within ${timeout}ms`);
                }

                // Wait and retry
                cy.wait(500);
                return checkEmail();
            });
    };

    return checkEmail();
});

Cypress.Commands.add('clearMailhog', () => {
    return cy.request({
        method: 'DELETE',
        url: 'http://localhost:8025/api/v1/messages'
    });
});
// cypress/integration/email_spec.js
describe('Email Functionality', () => {
    beforeEach(() => {
        cy.clearMailhog();
    });

    afterEach(() => {
        cy.clearMailhog();
    });

    it('should send welcome email', () => {
        const testEmail = `test${Date.now()}@example.com`;

        // Fill out registration form
        cy.visit('/register');
        cy.get('[data-cy=email]').type(testEmail);
        cy.get('[data-cy=password]').type('password123');
        cy.get('[data-cy=register-button]').click();

        // Verify email was sent
        cy.checkMailhogEmail({
            to: testEmail,
            subject: 'Welcome'
        }).then((email) => {
            expect(email.Subject).to.contain('Welcome');
            expect(email.From).to.contain('noreply');
        });
    });

    it('should send password reset email', () => {
        const testEmail = `reset${Date.now()}@example.com`;

        // Request password reset
        cy.visit('/forgot-password');
        cy.get('[data-cy=email]').type(testEmail);
        cy.get('[data-cy=reset-button]').click();

        // Verify password reset email
        cy.checkMailhogEmail({
            to: testEmail,
            subject: 'Password Reset'
        }).then((email) => {
            expect(email.Content.Body).to.contain('reset link');
        });
    });
});

Playwright Testing

// tests/helpers/mailhog.js
class MailHogClient {
    constructor(baseUrl = 'http://localhost:8025') {
        this.baseUrl = baseUrl;
        this.apiUrl = `${baseUrl}/api/v1`;
    }

    async getMessages() {
        const response = await fetch(`${this.apiUrl}/messages`);
        return response.json();
    }

    async searchMessages(query) {
        const response = await fetch(`${this.apiUrl}/search`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ query }),
        });
        return response.json();
    }

    async clearMessages() {
        const response = await fetch(`${this.apiUrl}/messages`, {
            method: 'DELETE',
        });
        return response.ok;
    }

    async waitForEmail(options = {}) {
        const { to, subject, timeout = 10000 } = options;
        const startTime = Date.now();

        while (Date.now() - startTime < timeout) {
            const messages = await this.getMessages();
            let foundMessages = messages.items;

            if (to) {
                foundMessages = foundMessages.filter(msg =>
                    msg.To.some(recipient => recipient.Address.includes(to))
                );
            }

            if (subject) {
                foundMessages = foundMessages.filter(msg =>
                    msg.Subject && msg.Subject.includes(subject)
                );
            }

            if (foundMessages.length > 0) {
                return foundMessages[0];
            }

            await new Promise(resolve => setTimeout(resolve, 500));
        }

        throw new Error(`Email not found within ${timeout}ms`);
    }
}

module.exports = { MailHogClient };
// tests/email.spec.js
const { test, expect } = require('@playwright/test');
const { MailHogClient } = require('./helpers/mailhog');

const mailhog = new MailHogClient();

test.beforeEach(async () => {
    await mailhog.clearMessages();
});

test.afterEach(async () => {
    await mailhog.clearMessages();
});

test('should send welcome email', async ({ page }) => {
    const testEmail = `test${Date.now()}@example.com`;

    // Register user
    await page.goto('/register');
    await page.fill('[data-cy=email]', testEmail);
    await page.fill('[data-cy=password]', 'password123');
    await page.click('[data-cy=register-button]');

    // Wait for welcome email
    const email = await mailhog.waitForEmail({
        to: testEmail,
        subject: 'Welcome'
    });

    expect(email.Subject).toContain('Welcome');
    expect(email.From).toContain('noreply');
});

test('should send notification email', async ({ page }) => {
    // Login and trigger notification
    await page.goto('/login');
    await page.fill('[data-cy=email]', 'existing@example.com');
    await page.fill('[data-cy=password]', 'password123');
    await page.click('[data-cy=login-button]');

    await page.click('[data-cy=trigger-notification]');

    // Check for notification email
    const email = await mailhog.waitForEmail({
        to: 'existing@example.com',
        subject: 'Notification'
    });

    expect(email.Content.Body).toContain('notification');
});

CI/CD Integration Patterns

GitHub Actions

# .github/workflows/email-tests.yml
name: Email Integration Tests

on: [push, pull_request]

jobs:
  test-emails:
    runs-on: ubuntu-latest

    services:
      mailhog:
        image: mailhog/mailhog
        ports:
          - 1025:1025
          - 8025:8025

    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Wait for MailHog
      run: |
        timeout 60 bash -c 'until curl -f http://localhost:8025/api/v1/messages; do sleep 2; done'

    - name: Run email tests
      run: npm run test:email
      env:
        NODE_ENV: test
        SMTP_HOST: localhost
        SMTP_PORT: 1025

Docker Compose Development

# docker-compose.dev.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - SMTP_HOST=mailhog
      - SMTP_PORT=1025
    depends_on:
      - mailhog
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev

  mailhog:
    image: mailhog/mailhog:latest
    ports:
      - "1025:1025"  # SMTP
      - "8025:8025"  # UI/API
    environment:
      - MH_HOSTNAME=mailhog.docker.local
    restart: unless-stopped

  # Optional: MongoDB for persistent storage
  mongodb:
    image: mongo:latest
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db
    restart: unless-stopped

volumes:
  mongodb_data:

Kubernetes Development Environment

# k8s/mailhog-dev.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: mailhog-dev
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mailhog
  namespace: mailhog-dev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mailhog
  template:
    metadata:
      labels:
        app: mailhog
    spec:
      containers:
      - name: mailhog
        image: mailhog/mailhog:latest
        ports:
        - containerPort: 1025
        - containerPort: 8025
        env:
        - name: MH_HOSTNAME
          value: "mailhog.k8s.dev"
        - name: MH_SMTP_BIND_ADDR
          value: "0.0.0.0:1025"
        - name: MH_UI_BIND_ADDR
          value: "0.0.0.0:8025"
        resources:
          requests:
            memory: "64Mi"
            cpu: "50m"
          limits:
            memory: "128Mi"
            cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
  name: mailhog-smtp
  namespace: mailhog-dev
spec:
  selector:
    app: mailhog
  ports:
  - port: 1025
    targetPort: 1025
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  name: mailhog-ui
  namespace: mailhog-dev
spec:
  selector:
    app: mailhog
  ports:
  - port: 8025
    targetPort: 8025
  type: LoadBalancer