Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:05:59 +08:00
commit 37120368e6
13 changed files with 5177 additions and 0 deletions

View File

@@ -0,0 +1,620 @@
# MailHog API Complete Reference
## Base URL
```
http://localhost:8025/api/v1
```
## Authentication
If authentication is enabled via `-auth-file`, include Basic Auth headers:
```bash
curl -u username:password http://localhost:8025/api/v1/messages
```
## Message Endpoints
### Get All Messages
```bash
curl http://localhost:8025/api/v1/messages
```
**Response Format:**
```json
{
"total": 2,
"count": 2,
"start": 0,
"items": [
{
"ID": "1234567890abcdef1234567890abcdef",
"From": "sender@example.com",
"To": [
{
"Address": "recipient@example.com",
"Name": "Recipient Name"
}
],
"Subject": "Test Email",
"Date": "2023-01-01T12:00:00Z",
"Content": {
"Headers": {
"Content-Type": ["text/plain; charset=utf-8"],
"Subject": ["Test Email"],
"From": ["sender@example.com"],
"To": ["recipient@example.com"]
},
"Body": "This is the email body",
"Size": 1024
},
"MIME": {
"Parts": [...]
},
"Created": "2023-01-01T12:00:00Z"
}
]
}
```
### Get Specific Message
```bash
curl http://localhost:8025/api/v1/messages/{message-id}
```
**Example:**
```bash
curl http://localhost:8025/api/v1/messages/1234567890abcdef1234567890abcdef
```
### Get Messages with Limits
```bash
# Get first 10 messages
curl "http://localhost:8025/api/v1/messages?limit=10"
# Get messages with offset (pagination)
curl "http://localhost:8025/api/v1/messages?start=10&limit=10"
# Get recent messages (sorted by creation date)
curl "http://localhost:8025/api/v1/messages?start=0&limit=20"
```
### Delete All Messages
```bash
curl -X DELETE http://localhost:8025/api/v1/messages
```
### Delete Specific Message
```bash
curl -X DELETE http://localhost:8025/api/v1/messages/{message-id}
```
## Search Endpoints
### Search Messages
```bash
curl -X POST http://localhost:8025/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "search term"}'
```
**Advanced Search Examples:**
```bash
# Search by subject
curl -X POST http://localhost:8025/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "subject:welcome"}'
# Search by sender
curl -X POST http://localhost:8025/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "from:user@example.com"}'
# Search by recipient
curl -X POST http://localhost:8025/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "to:recipient@example.com"}'
# Search body content
curl -X POST http://localhost:8025/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "content:confirmation link"}'
```
**Search Response:**
```json
{
"total": 1,
"count": 1,
"start": 0,
"items": [...]
}
```
## MIME and Attachment Endpoints
### Get MIME Content
```bash
curl http://localhost:8025/api/v1/messages/{message-id}/mime
```
### Download Attachment
```bash
curl http://localhost:8025/api/v1/messages/{message-id}/attachments/{attachment-id}
```
### List Attachments
```bash
curl http://localhost:8025/api/v1/messages/{message-id}/attachments
```
## Release Endpoints (Outgoing SMTP)
### Release One Message
```bash
curl -X POST http://localhost:8025/api/v1/messages/{message-id}/release
```
**With Custom Recipients:**
```bash
curl -X POST http://localhost:8025/api/v1/messages/{message-id}/release \
-H "Content-Type: application/json" \
-d '{"recipients": ["alt@example.com"]}'
```
### Release Multiple Messages
```bash
curl -X POST http://localhost:8025/api/v1/messages/release \
-H "Content-Type: application/json" \
-d '{"ids": ["id1", "id2", "id3"]}'
```
## Status and Health Endpoints
### Get Server Status
```bash
curl http://localhost:8025/api/v1/status
```
**Response:**
```json
{
"messages": 42,
"storage": "memory",
"version": "v1.0.0"
}
```
### Health Check
```bash
curl -f http://localhost:8025/api/v1/health
```
## Utility Endpoints
### Get Raw Email Source
```bash
curl http://localhost:8025/api/v1/messages/{message-id}/raw
```
### Parse Email Headers
```bash
curl http://localhost:8025/api/v1/messages/{message-id}/headers
```
## WebSockets
### Real-time Message Updates
```javascript
const ws = new WebSocket('ws://localhost:8025/api/v1/websocket');
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
switch(data.action) {
case 'messages':
console.log('New messages:', data.messages);
break;
case 'deleted':
console.log('Deleted messages:', data.ids);
break;
case 'released':
console.log('Released messages:', data.ids);
break;
}
};
```
**WebSocket Message Formats:**
**New Message Notification:**
```json
{
"action": "messages",
"messages": [
{
"ID": "message-id",
"From": "sender@example.com",
"To": [{"Address": "recipient@example.com"}],
"Subject": "Test",
"Created": "2023-01-01T12:00:00Z"
}
]
}
```
**Message Deleted:**
```json
{
"action": "deleted",
"ids": ["message-id"]
}
```
**Message Released:**
```json
{
"action": "released",
"ids": ["message-id"]
}
```
## JavaScript Client Examples
### Basic API Client
```javascript
class MailHogClient {
constructor(baseUrl = 'http://localhost:8025', auth = null) {
this.baseUrl = baseUrl;
this.auth = auth;
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}/api/v1${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
};
if (this.auth) {
config.headers.Authorization = `Basic ${btoa(this.auth)}`;
}
const response = await fetch(url, config);
return response.json();
}
async getMessages(limit = 50, start = 0) {
return this.request(`/messages?limit=${limit}&start=${start}`);
}
async getMessage(id) {
return this.request(`/messages/${id}`);
}
async searchMessages(query) {
return this.request('/search', {
method: 'POST',
body: JSON.stringify({ query })
});
}
async deleteMessages(ids) {
if (ids === 'all') {
return this.request('/messages', { method: 'DELETE' });
}
return this.request(`/messages/${ids}`, { method: 'DELETE' });
}
async releaseMessage(id, recipients = null) {
const body = recipients ? { recipients } : undefined;
return this.request(`/messages/${id}/release`, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined
});
}
}
// Usage
const client = new MailHogClient();
// Get all messages
client.getMessages().then(data => console.log(data.total));
// Search for specific emails
client.searchMessages('subject:welcome').then(data => console.log(data.items));
```
### WebSocket Integration
```javascript
class MailHogWebSocket {
constructor(baseUrl = 'ws://localhost:8025') {
this.baseUrl = baseUrl;
this.callbacks = {};
}
connect() {
this.ws = new WebSocket(`${this.baseUrl}/api/v1/websocket`);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (this.callbacks[data.action]) {
this.callbacks[data.action](data);
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onclose = () => {
console.log('WebSocket connection closed');
// Auto-reconnect
setTimeout(() => this.connect(), 5000);
};
}
on(action, callback) {
this.callbacks[action] = callback;
}
onNewMessages(callback) {
this.on('messages', callback);
}
onMessagesDeleted(callback) {
this.on('deleted', callback);
}
onMessagesReleased(callback) {
this.on('released', callback);
}
}
// Usage
const ws = new MailHogWebSocket();
ws.connect();
ws.onNewMessages((data) => {
console.log('New messages received:', data.messages);
});
```
## Python Client Examples
### Requests-based Client
```python
import requests
import json
from typing import List, Dict, Optional
class MailHogClient:
def __init__(self, base_url: str = "http://localhost:8025", auth: tuple = None):
self.base_url = base_url
self.auth = auth
self.api_url = f"{base_url}/api/v1"
def _request(self, endpoint: str, method: str = "GET", data: dict = None) -> dict:
url = f"{self.api_url}{endpoint}"
response = requests.request(
method,
url,
json=data,
auth=self.auth
)
response.raise_for_status()
return response.json()
def get_messages(self, limit: int = 50, start: int = 0) -> dict:
return self._request(f"/messages?limit={limit}&start={start}")
def get_message(self, message_id: str) -> dict:
return self._request(f"/messages/{message_id}")
def search_messages(self, query: str) -> dict:
return self._request("/search", method="POST", data={"query": query})
def delete_messages(self, message_ids: str = "all") -> dict:
if message_ids == "all":
return self._request("/messages", method="DELETE")
return self._request(f"/messages/{message_ids}", method="DELETE")
def release_message(self, message_id: str, recipients: List[str] = None) -> dict:
data = {"recipients": recipients} if recipients else None
return self._request(f"/messages/{message_id}/release", method="POST", data=data)
# Usage
client = MailHogClient()
# Get all messages
messages = client.get_messages()
print(f"Total messages: {messages['total']}")
# Search for specific emails
search_results = client.search_messages("subject:welcome")
print(f"Found {search_results['total']} messages")
```
## Testing Examples
### Integration Test Example
```javascript
// Jest/Mocha integration test example
describe('Email Functionality', () => {
const mailhog = new MailHogClient();
beforeEach(async () => {
// Clear all messages before each test
await mailhog.deleteMessages('all');
});
afterEach(async () => {
// Clean up after each test
await mailhog.deleteMessages('all');
});
it('should send welcome email', async () => {
// Trigger email sending in your application
await sendWelcomeEmail('test@example.com');
// Wait for email to be processed
await new Promise(resolve => setTimeout(resolve, 1000));
// Verify email was received
const messages = await mailhog.searchMessages('to:test@example.com');
expect(messages.total).toBe(1);
const message = messages.items[0];
expect(message.Subject).toContain('Welcome');
expect(message.From).toContain('noreply@yourapp.com');
});
it('should handle bulk email sending', async () => {
const recipients = ['user1@test.com', 'user2@test.com', 'user3@test.com'];
// Send bulk emails
await sendBulkNotifications(recipients);
// Wait for processing
await new Promise(resolve => setTimeout(resolve, 3000));
// Verify all emails were received
const messages = await mailhog.getMessages();
expect(messages.total).toBe(recipients.length);
// Verify recipients
const recipientAddresses = messages.items.flatMap(msg =>
msg.To.map(to => to.Address)
);
recipients.forEach(recipient => {
expect(recipientAddresses).toContain(recipient);
});
});
});
```
### Load Testing Example
```bash
#!/bin/bash
# load_test_mailhog.sh
BASE_URL="http://localhost:8025"
CONCURRENT_REQUESTS=50
TOTAL_REQUESTS=1000
echo "Starting MailHog API load test..."
# Create temp file for results
RESULT_FILE=$(mktemp)
# Run concurrent curl requests
for ((i=1; i<=CONCURRENT_REQUESTS; i++)); do
{
for ((j=1; j<=TOTAL_REQUESTS/CONCURRENT_REQUESTS; j++)); do
start_time=$(date +%s%N)
curl -s -o /dev/null -w "%{http_code},%{time_total}" \
"$BASE_URL/api/v1/messages" >> "$RESULT_FILE"
echo ",$(( ($(date +%s%N) - start_time) / 1000000 ))" >> "$RESULT_FILE"
done
} &
done
# Wait for all background processes
wait
# Analyze results
echo "Load test completed. Analyzing results..."
awk -F',' '
{
status[$1]++
if ($1 == "200") {
sum_time += $2
sum_response += $3
count++
}
}
END {
printf "Success Rate: %.2f%%\n", (status["200"]/NR)*100
printf "Average Response Time: %.3f ms\n", sum_time/count*1000
printf "Average Server Response Time: %.3f ms\n", sum_response/count
for (code in status) {
printf "HTTP %s: %d requests\n", code, status[code]
}
}' "$RESULT_FILE"
rm "$RESULT_FILE"
```
## Error Handling
### Common HTTP Status Codes
- **200 OK**: Request successful
- **400 Bad Request**: Invalid request parameters
- **401 Unauthorized**: Authentication required
- **404 Not Found**: Message or endpoint not found
- **500 Internal Server Error**: Server error
### Error Response Format
```json
{
"error": "Error message description",
"code": "ERROR_CODE"
}
```
### Retry Logic Example
```javascript
async function robustMailHogRequest(client, endpoint, options = {}) {
const maxRetries = 3;
const retryDelay = 1000;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await client.request(endpoint, options);
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
if (error.status >= 500 || error.status === 0) {
// Retry on server errors or connection issues
console.log(`Attempt ${attempt} failed, retrying in ${retryDelay}ms...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
continue;
}
// Don't retry on client errors
throw error;
}
}
}
```

View File

@@ -0,0 +1,378 @@
# MailHog Advanced Configurations
## Complete Configuration Reference
### Basic Configuration
```bash
# Minimal configuration
mailhog
# Custom ports
mailhog -smtp-bind-addr :1025 -ui-bind-addr :8025 -api-bind-addr :8025
```
### Storage Configurations
#### Memory Storage (Default)
```bash
mailhog -storage memory
```
#### MongoDB Storage
```bash
# Basic MongoDB
mailhog -storage mongodb -mongo-uri 127.0.0.1:27017 -mongo-db mailhog -mongo-coll messages
# With authentication
mailhog -storage mongodb \
-mongo-uri "mongodb://user:pass@mongodb.example.com:27017/mailhog_prod" \
-mongo-db mailhog_prod \
-mongo-coll messages
# With replica set
mailhog -storage mongodb \
-mongo-uri "mongodb://mongodb1:27017,mongodb2:27017,mongodb3:27017/mailhog?replicaSet=rs0" \
-mongo-db mailhog_prod \
-mongo-coll messages
```
#### Maildir Storage
```bash
mailhog -storage maildir -maildir-path /var/mailhog/storage
```
### Network Configuration
#### Bind Addresses
```bash
# Bind to all interfaces
mailhog -smtp-bind-addr 0.0.0.0:1025 -ui-bind-addr 0.0.0.0:8025
# Bind to specific interface
mailhog -smtp-bind-addr 192.168.1.100:1025 -ui-bind-addr 192.168.1.100:8025
```
#### Hostname Configuration
```bash
mailhog -hostname mailhog.production.local
```
#### CORS Configuration
```bash
# Single origin
mailhog -cors-origin "https://app.example.com"
# Multiple origins
mailhog -cors-origin "https://app.example.com,https://admin.example.com"
```
### Authentication Configuration
#### Basic Auth File Format
```
# auth-file.txt format
username1:$2b$12$hashedpassword1
username2:$2b$12$hashedpassword2
```
#### Generate Bcrypt Hash
```bash
# Python
python3 -c "import bcrypt; print(bcrypt.hashpw(b'password123', bcrypt.gensalt()).decode())"
# Node.js
node -e "const bcrypt = require('bcrypt'); console.log(bcrypt.hashSync('password123', 12));"
```
### Outgoing SMTP Configuration
#### Gmail Configuration
```json
{
"server": "smtp.gmail.com",
"port": 587,
"username": "your-email@gmail.com",
"password": "app-specific-password",
"tls": true,
"from": "your-email@gmail.com"
}
```
#### Office 365 Configuration
```json
{
"server": "smtp.office365.com",
"port": 587,
"username": "your-email@company.com",
"password": "your-password",
"tls": true,
"from": "your-email@company.com"
}
```
#### Local SMTP Relay
```json
{
"server": "localhost",
"port": 25,
"tls": false,
"from": "noreply@company.local"
}
```
### Jim Network Simulation Configuration
#### Comprehensive Jim Configuration
```bash
mailhog -invite-jim \
-jim-accept 0.98 \
-jim-reject-sender 0.05 \
-jim-reject-recipient 0.05 \
-jim-reject-auth 0.03 \
-jim-disconnect 0.01 \
-jim-linkspeed-affect 0.15 \
-jim-linkspeed-min 512 \
-jim-linkspeed-max 20480
```
#### Jim Parameter Explanations
- **jim-accept**: Probability of accepting connection (0.0-1.0)
- **jim-reject-sender**: Probability of rejecting MAIL FROM command
- **jim-reject-recipient**: Probability of rejecting RCPT TO command
- **jim-reject-auth**: Probability of rejecting AUTH command
- **jim-disconnect**: Probability of random disconnection
- **jim-linkspeed-affect**: Probability of affecting link speed
- **jim-linkspeed-min/ max**: Range for simulated link speed (bytes/sec)
### Production Configurations
#### High-Availability Setup
```bash
mailhog \
-smtp-bind-addr 0.0.0.0:1025 \
-ui-bind-addr 0.0.0.0:8025 \
-api-bind-addr 0.0.0.0:8025 \
-storage mongodb \
-mongo-uri "mongodb://mongodb1:27017,mongodb2:27017,mongodb3:27017/mailhog_prod?replicaSet=rs0" \
-mongo-db mailhog_prod \
-mongo-coll messages \
-cors-origin "https://app.example.com,https://admin.example.com" \
-hostname mailhog.production.local \
-auth-file /etc/mailhog/auth.txt \
-outgoing-smtp /etc/mailhog/outgoing.json
```
#### Development Environment
```bash
mailhog \
-smtp-bind-addr 127.0.0.1:1025 \
-ui-bind-addr 127.0.0.1:8025 \
-api-bind-addr 127.0.0.1:8025 \
-storage memory \
-hostname mailhog.dev.local \
-cors-origin "http://localhost:3000,http://localhost:5173"
```
#### CI/CD Environment
```bash
mailhog \
-smtp-bind-addr 0.0.0.0:1025 \
-ui-bind-addr 0.0.0.0:8025 \
-api-bind-addr 0.0.0.0:8025 \
-storage memory \
-hostname mailhog.ci.local \
-ui-web-path mailhog
```
### Docker Configurations
#### Dockerfile Example
```dockerfile
FROM mailhog/mailhog:latest
# Copy custom configuration
COPY auth.txt /auth.txt
COPY outgoing.json /outgoing.json
# Custom entrypoint for additional config
ENTRYPOINT ["/bin/sh", "-c", "MailHog -auth-file /auth.txt -outgoing-smtp /outgoing.json"]
```
#### Docker Compose Example
```yaml
version: '3.8'
services:
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025"
- "8025:8025"
environment:
- MH_HOSTNAME=mailhog.docker.local
volumes:
- ./auth.txt:/auth.txt
- ./outgoing.json:/outgoing.json
command: ["MailHog", "-auth-file", "/auth.txt", "-outgoing-smtp", "/outgoing.json"]
mongodb:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
volumes:
mongodb_data:
```
### Kubernetes Configurations
#### Basic Deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailhog
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.local"
- name: MH_SMTP_BIND_ADDR
value: "0.0.0.0:1025"
- name: MH_UI_BIND_ADDR
value: "0.0.0.0:8025"
---
apiVersion: v1
kind: Service
metadata:
name: mailhog-smtp
spec:
selector:
app: mailhog
ports:
- port: 1025
targetPort: 1025
---
apiVersion: v1
kind: Service
metadata:
name: mailhog-ui
spec:
selector:
app: mailhog
ports:
- port: 8025
targetPort: 8025
```
### Performance Tuning
#### High-Volume Configuration
```bash
mailhog \
-smtp-bind-addr 0.0.0.0:1025 \
-ui-bind-addr 0.0.0.0:8025 \
-storage mongodb \
-mongo-uri "mongodb://mongodb.example.com:27017/mailhog_perf" \
-mongo-db mailhog_perf \
-mongo-coll messages \
-hostname mailhog.perf.local
```
#### MongoDB Indexing for Performance
```javascript
// Connect to MongoDB and create indexes
use mailhog_perf
// Compound index for common queries
db.messages.createIndex({ "created": -1, "From": 1 })
// Index for sender searches
db.messages.createIndex({ "From": 1, "created": -1 })
// Index for recipient searches
db.messages.createIndex({ "To.Address": 1, "created": -1 })
// Text index for full-text search
db.messages.createIndex({
"Content.Body": "text",
"Subject": "text",
"From": "text",
"To.Address": "text"
})
```
### Environment Variables
MailHog supports environment variable configuration. All command-line flags can be set as environment variables:
```bash
# Environment variable naming convention: MH_<FLAG_NAME_IN_UPPERCASE>
export MH_SMTP_BIND_ADDR=0.0.0.0:1025
export MH_UI_BIND_ADDR=0.0.0.0:8025
export MH_HOSTNAME=mailhog.env.local
export MH_STORAGE=mongodb
export MH_MONGO_URI=mongodb://localhost:27017/mailhog
export MH_MONGO_DB=mailhog
export MH_MONGO_COLL=messages
# Run with environment variables
mailhog
```
### Configuration Validation
#### Validate Configuration Before Start
```bash
# Test MongoDB connection
mongo --eval "db.adminCommand('ismaster')" mongodb://localhost:27017/mailhog
# Test file permissions for auth file
test -r /path/to/auth.txt && echo "Auth file readable" || echo "Auth file not readable"
# Test port availability
netstat -tuln | grep :1025 && echo "Port 1025 in use" || echo "Port 1025 available"
netstat -tuln | grep :8025 && echo "Port 8025 in use" || echo "Port 8025 available"
```
#### Configuration Health Check Script
```bash
#!/bin/bash
# config_health_check.sh
echo "MailHog Configuration Health Check"
# Check required commands
command -v mailhog >/dev/null 2>&1 || { echo "MailHog not installed"; exit 1; }
# Test MongoDB if configured
if [[ "$MH_STORAGE" == "mongodb" ]]; then
mongo --eval "db.adminCommand('ismaster')" "$MH_MONGO_URI" >/dev/null 2>&1 || { echo "MongoDB connection failed"; exit 1; }
echo "✓ MongoDB connection OK"
fi
# Test auth file if configured
if [[ -n "$MH_AUTH_FILE" ]]; then
test -r "$MH_AUTH_FILE" || { echo "Auth file not readable: $MH_AUTH_FILE"; exit 1; }
echo "✓ Auth file readable"
fi
echo "✓ Configuration health check passed"
```

File diff suppressed because it is too large Load Diff