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,11 @@
{
"name": "mailhog",
"description": "Comprehensive MailHog email testing server management toolkit with SMTP testing, email capture, development integration, and automated email workflow testing",
"version": "1.0.0",
"author": {
"name": "Will Hampson"
},
"skills": [
"./skills"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# mailhog
Comprehensive MailHog email testing server management toolkit with SMTP testing, email capture, development integration, and automated email workflow testing

81
plugin.lock.json Normal file
View File

@@ -0,0 +1,81 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:Whamp/whamp-claude-tools:mailhog-plugin",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "1b3ac23bcbd20e7c3046a8e98603537e03dfd90a",
"treeHash": "2b88e46ab5c43dec9427a686687d9d52156f23c69f399d441d55a6ec0c51bf2d",
"generatedAt": "2025-11-28T10:12:57.441964Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "mailhog",
"description": "Comprehensive MailHog email testing server management toolkit with SMTP testing, email capture, development integration, and automated email workflow testing",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "ceb4c71476b164df46ed4974dc6e1370a40bdf573b9e17d63d23fd4aa865aa1f"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "6153944b411ef4f6b2359242d0675d76add142311a895b02787a4319968ac1a9"
},
{
"path": "skills/mailhog/SKILL.md",
"sha256": "60035b9b2c99d2844d77ab3cb97802660d01c015e7833599340d0e9c41489b1c"
},
{
"path": "skills/mailhog/references/integration-patterns.md",
"sha256": "1e006488be01ae27a120fe075ce1ae1286c8738fff095ea13f7d45b98cf7e13f"
},
{
"path": "skills/mailhog/references/configurations.md",
"sha256": "309e83f8097caa399907d91e421631046c1b205972b5b6773a43e3ed2daa1162"
},
{
"path": "skills/mailhog/references/api-endpoints.md",
"sha256": "e4cb9dbd23326be5896d5f1cf0b1a7500882259f83d5a16e299ddf880cddbada"
},
{
"path": "skills/mailhog/examples/docker-compose.yml",
"sha256": "56c892f620361e295a21cd09766f4a8c35e400192ef0705186b0291b9b372526"
},
{
"path": "skills/mailhog/examples/app-configs/python-smtp.py",
"sha256": "75a6277771213720c187339e93293a28d03568ddb5144d045f57b8f25f013bd7"
},
{
"path": "skills/mailhog/examples/app-configs/nodemailer.js",
"sha256": "4d58e916304a1d1634f2f9a89e0d988cfa97f24c83e9347b3a09f35010e513d6"
},
{
"path": "skills/mailhog/scripts/mailhog_manager.sh",
"sha256": "839a849ed4fc53eac477947a510db949eaf1590ac905d6c6bee8af448a45e5a3"
},
{
"path": "skills/mailhog/scripts/send_test_email.sh",
"sha256": "819900958de58ce6c4e33dc16e6a95fdd8301c4ddcdc0b16b4c2b326b13be352"
},
{
"path": "skills/mailhog/scripts/test_email_workflow.sh",
"sha256": "ae08a1204fb4d24cf6c92ed088455f74bd5b45a083e9d53aec4d92bbd56cf1ad"
}
],
"dirSha256": "2b88e46ab5c43dec9427a686687d9d52156f23c69f399d441d55a6ec0c51bf2d"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

363
skills/mailhog/SKILL.md Normal file
View File

@@ -0,0 +1,363 @@
---
name: mailhog
description: This skill should be used when the user asks to "set up MailHog", "test email functionality", "configure MailHog", "send test emails", "check MailHog messages", "configure SMTP testing", or "manage email capture". Provides comprehensive MailHog email testing server setup and management.
version: 1.0.0
---
# MailHog Email Testing Server Management
MailHog provides a comprehensive email testing solution for development and testing environments. This skill enables complete MailHog server management, email testing workflows, and integration with development pipelines.
## Core Concepts
**MailHog Architecture**: MailHog consists of SMTP server (port 1025 by default) for receiving emails and HTTP API/UI (port 8025 by default) for viewing and managing captured emails.
**Storage Options**: Memory (default, ephemeral), MongoDB (persistent), or Maildir (file-based) for email storage.
**Jim Simulation**: Built-in chaotic network simulation for testing email resilience under adverse conditions.
## Essential Setup Commands
### Start Basic MailHog Server
Start MailHog with default settings (memory storage, ports 1025/8025):
```bash
# Basic start
mailhog
# Custom ports
mailhog -smtp-bind-addr :1025 -ui-bind-addr :8025 -api-bind-addr :8025
# Background execution
mailhog &
```
### Start with Persistent Storage
Configure MongoDB storage:
```bash
mailhog -storage mongodb -mongo-uri 127.0.0.1:27017 -mongo-db mailhog_test -mongo-coll messages
```
Configure Maildir storage:
```bash
mailhog -storage maildir -maildir-path ./mail_storage
```
## Configuration Management
### Custom SMTP Configuration
```bash
# Custom hostname for EHLO/HELO
mailhog -hostname mailhog.test.local
# Custom bind addresses
mailhog -smtp-bind-addr 0.0.0.0:1025 -ui-bind-addr 0.0.0.0:8025
# CORS configuration
mailhog -cors-origin "http://localhost:3000,http://localhost:5173"
```
### Authentication Setup
```bash
# Create bcrypt password file
echo "admin:$(bcrypt-hash 'password123')" > auth_file.txt
# Enable authentication
mailhog -auth-file auth_file.txt
```
### Outgoing SMTP Configuration
Create outgoing SMTP configuration for email forwarding:
```json
{
"server": "smtp.gmail.com",
"port": 587,
"username": "test@gmail.com",
"password": "app-password",
"tls": true
}
```
```bash
mailhog -outgoing-smtp outgoing_config.json
```
## Testing Email Functionality
### Send Test Emails via CLI
Use built-in test utilities or SMTP clients:
```bash
# Using telnet for basic SMTP test
telnet localhost 1025
EHLO test.local
MAIL FROM:test@sender.local
RCPT TO:recipient@test.local
DATA
Subject: Test Email
From: test@sender.local
To: recipient@test.local
This is a test email.
.
QUIT
```
### Send Test Emails with Scripts
Execute test email sending script:
```bash
./scripts/send_test_email.sh --to test@recipient.local --subject "Test Message" --body "Test content"
```
### API Interaction Examples
Query captured emails via API:
```bash
# Get all messages
curl http://localhost:8025/api/v1/messages
# Get recent messages
curl http://localhost:8025/api/v1/messages?limit=10
# Search emails
curl -X POST http://localhost:8025/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "subject:test"}'
```
## Development Integration
### Environment Setup for Testing
Configure application environments for MailHog integration:
```bash
# Environment variables
export SMTP_HOST=localhost
export SMTP_PORT=1025
export SMTP_USER=
export SMTP_PASS=
export SMTP_TLS=false
```
### Framework Integration Patterns
**Node.js/Nodemailer Configuration:**
```javascript
const transporter = nodemailer.createTransporter({
host: 'localhost',
port: 1025,
secure: false,
auth: false
});
```
**Python/SMTP Configuration:**
```python
import smtplib
server = smtplib.SMTP('localhost', 1025)
server.sendmail(sender, recipients, message)
```
### Testing Workflow Automation
Execute automated testing script:
```bash
./scripts/test_email_workflow.sh --config test_config.json
```
## Advanced Configuration
### Network Simulation (Jim)
Enable Jim for chaotic network testing:
```bash
# Enable Jim with default settings
mailhog -invite-jim
# Custom Jim behavior
mailhog -invite-jim \
-jim-accept 0.95 \
-jim-reject-sender 0.1 \
-jim-reject-recipient 0.1 \
-jim-disconnect 0.02 \
-jim-linkspeed-affect 0.2
```
### Production-Ready Configuration
```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.example.com:27017 \
-mongo-db mailhog_prod \
-cors-origin "https://app.example.com" \
-hostname mailhog.example.com
```
## Monitoring and Debugging
### Health Checks
Verify MailHog is running correctly:
```bash
# Check SMTP port
telnet localhost 1025
# Check UI/API
curl http://localhost:8025
# Check API status
curl http://localhost:8025/api/v1/messages
```
### Log Analysis
Monitor MailHog operations and troubleshoot issues:
```bash
# Start with verbose logging (if available)
mailhog 2>&1 | tee mailhog.log
# Monitor message count
watch -n 5 'curl -s http://localhost:8025/api/v1/messages | jq ".total"'
```
### Performance Optimization
Optimize for high-volume testing:
```bash
# Memory cleanup for long-running instances
curl -X DELETE http://localhost:8025/api/v1/messages
# MongoDB indexing for performance
# Execute via mongo shell on mailhog database
db.messages.createIndex({ "created": -1 })
db.messages.createIndex({ "From": 1 })
```
## Common Workflows
### Development Email Testing Setup
1. Start MailHog with appropriate configuration
2. Configure application SMTP settings to point to MailHog
3. Send test emails from application
4. Verify emails in MailHog UI (`http://localhost:8025`)
5. Use API for automated testing assertions
### Automated CI/CD Integration
1. Include MailHog in test docker-compose or CI configuration
2. Configure test environment SMTP settings
3. Run application tests that send emails
4. Use API assertions to verify email content/delivery
5. Clean up messages between test runs
### Bulk Email Testing
1. Use scripts to generate multiple test emails
2. Test rate limiting and performance
3. Verify email queue handling
4. Monitor resource usage during high-volume scenarios
## Troubleshooting
### Common Issues
**Port Conflicts**: Kill existing processes or change ports:
```bash
lsof -i :1025 -i :8025
kill -9 <PID>
```
**MongoDB Connection Issues**: Verify MongoDB is running and accessible:
```bash
mongo mongodb://127.0.0.1:27017/mailhog_test
```
**Email Not Appearing**: Check SMTP client configuration and network connectivity.
**UI Not Loading**: Verify API bind address and check for port conflicts.
### Debug Commands
```bash
# Test SMTP connection manually
telnet localhost 1025
# Check MailHog process
ps aux | grep mailhog
# Verify API accessibility
curl -v http://localhost:8025/api/v1/messages
```
## Security Considerations
**Production Usage**: Never expose MailHog directly to internet without authentication and proper firewall configuration.
**Email Privacy**: Captured emails may contain sensitive information - secure MailHog instance appropriately.
**Authentication**: Always use authentication in production environments with `-auth-file`.
## Migration and Backup
### MongoDB Backup
```bash
# Backup MailHog messages
mongodump --db mailhog_prod --collection messages --out ./backup
# Restore messages
mongorestore --db mailhog_prod --collection messages ./backup/messages.bson
```
### Maildir Backup
```bash
# Backup maildir storage
tar -czf mailhog_maildir_backup.tar.gz ./mail_storage
```
## Additional Resources
### Reference Files
For detailed configurations and advanced patterns, consult:
- **`references/configurations.md`** - Advanced configuration examples
- **`references/api-endpoints.md`** - Complete API reference
- **`references/integration-patterns.md`** - Framework integration guides
### Scripts
Utility scripts for common operations:
- **`scripts/send_test_email.sh`** - Send test emails via SMTP
- **`scripts/test_email_workflow.sh`** - Automated testing workflow
- **`scripts/mailhog_manager.sh`** - Server management utility
### Examples
Working configurations and setups:
- **`examples/docker-compose.yml`** - Docker container setup
- **`examples/app-configs/`** - Application configuration examples
- **`examples/terraform/`** - Infrastructure as code examples

View File

@@ -0,0 +1,97 @@
// Node.js/Nodemailer configuration for MailHog
const nodemailer = require('nodemailer');
// Development configuration (using MailHog)
const createDevTransporter = () => {
return nodemailer.createTransporter({
host: 'localhost',
port: 1025,
secure: false, // TLS not required for MailHog
auth: false, // No authentication required
tls: {
rejectUnauthorized: false
},
logger: true, // Enable logging for debugging
debug: true // Show debug output
});
};
// Production configuration (using real SMTP)
const createProdTransporter = () => {
return nodemailer.createTransporter({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587'),
secure: process.env.SMTP_SECURE === 'true', // true for 465, false for other ports
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
},
tls: {
rejectUnauthorized: true
}
});
};
// Get appropriate transporter based on environment
const getTransporter = () => {
if (process.env.NODE_ENV === 'production') {
return createProdTransporter();
} else {
return createDevTransporter();
}
};
// Email sending function
const sendEmail = async (options) => {
try {
const transporter = getTransporter();
const mailOptions = {
from: options.from || process.env.EMAIL_FROM || 'test@sender.local',
to: options.to,
subject: options.subject,
text: options.text,
html: options.html,
attachments: options.attachments
};
const info = await transporter.sendMail(mailOptions);
console.log('Email sent successfully:', {
messageId: info.messageId,
response: info.response,
accepted: info.accepted,
rejected: info.rejected
});
return info;
} catch (error) {
console.error('Email sending failed:', error);
throw error;
}
};
// Example usage
if (require.main === module) {
(async () => {
try {
await sendEmail({
to: 'test@recipient.local',
subject: 'Test Email from Nodemailer',
text: 'This is a test email sent via Nodemailer',
html: '<h1>Test Email</h1><p>This email was sent using Nodemailer with MailHog.</p>'
});
console.log('Test email sent successfully');
} catch (error) {
console.error('Failed to send test email:', error);
}
})();
}
module.exports = {
sendEmail,
createDevTransporter,
createProdTransporter,
getTransporter
};

View File

@@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""
Python SMTP configuration for MailHog
This module provides easy email sending functionality that works
with both MailHog in development and real SMTP servers in production.
"""
import smtplib
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from typing import Optional, List, Dict, Any
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class EmailSender:
"""Email sender class that works with MailHog and real SMTP servers"""
def __init__(self):
self.smtp_host = os.getenv('SMTP_HOST', 'localhost')
self.smtp_port = int(os.getenv('SMTP_PORT', 1025))
self.smtp_user = os.getenv('SMTP_USER')
self.smtp_pass = os.getenv('SMTP_PASS')
self.use_tls = os.getenv('SMTP_USE_TLS', 'false').lower() == 'true'
self.from_email = os.getenv('EMAIL_FROM', 'test@sender.local')
def create_connection(self):
"""Create SMTP connection based on environment"""
if os.getenv('NODE_ENV') == 'production':
# Production SMTP configuration
server = smtplib.SMTP(self.smtp_host, self.smtp_port)
if self.use_tls:
server.starttls()
if self.smtp_user and self.smtp_pass:
server.login(self.smtp_user, self.smtp_pass)
else:
# Development with MailHog
server = smtplib.SMTP('localhost', 1025)
# MailHog doesn't require authentication or TLS
return server
def send_text_email(self, to_email: str, subject: str, body: str) -> bool:
"""Send plain text email"""
try:
msg = MIMEText(body, 'plain', 'utf-8')
msg['Subject'] = subject
msg['From'] = self.from_email
msg['To'] = to_email
with self.create_connection() as server:
server.send_message(msg)
logger.info(f"Text email sent successfully to {to_email}")
return True
except Exception as e:
logger.error(f"Failed to send text email: {e}")
return False
def send_html_email(self, to_email: str, subject: str, html_body: str,
text_body: Optional[str] = None) -> bool:
"""Send HTML email with optional plain text fallback"""
try:
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = self.from_email
msg['To'] = to_email
# Add plain text part
if text_body:
text_part = MIMEText(text_body, 'plain', 'utf-8')
msg.attach(text_part)
else:
# Convert HTML to plain text (basic conversion)
import re
plain_text = re.sub(r'<[^>]+>', '', html_body)
plain_text = re.sub(r'\s+', ' ', plain_text).strip()
text_part = MIMEText(plain_text, 'plain', 'utf-8')
msg.attach(text_part)
# Add HTML part
html_part = MIMEText(html_body, 'html', 'utf-8')
msg.attach(html_part)
with self.create_connection() as server:
server.send_message(msg)
logger.info(f"HTML email sent successfully to {to_email}")
return True
except Exception as e:
logger.error(f"Failed to send HTML email: {e}")
return False
def send_email_with_attachments(self, to_email: str, subject: str,
body: str, attachments: List[str]) -> bool:
"""Send email with file attachments"""
try:
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = self.from_email
msg['To'] = to_email
# Add body
msg.attach(MIMEText(body, 'plain', 'utf-8'))
# Add attachments
for file_path in attachments:
if os.path.exists(file_path):
with open(file_path, 'rb') as attachment:
part = MIMEBase('application', 'octet-stream')
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
('attachment; filename=' +
f'{os.path.basename(file_path)}')
)
msg.attach(part)
else:
logger.warning(f"Attachment file not found: {file_path}")
with self.create_connection() as server:
server.send_message(msg)
logger.info(
f"Email with attachments sent successfully to {to_email}"
)
return True
except Exception as e:
logger.error(f"Failed to send email with attachments: {e}")
return False
def send_bulk_emails(self, recipients: List[str], subject: str, body: str,
is_html: bool = False) -> Dict[str, Any]:
"""Send bulk emails to multiple recipients"""
results = {
'total': len(recipients),
'successful': 0,
'failed': 0,
'errors': []
}
for recipient in recipients:
try:
if is_html:
success = self.send_html_email(recipient, subject, body)
else:
success = self.send_text_email(recipient, subject, body)
if success:
results['successful'] += 1
else:
results['failed'] += 1
results['errors'].append(f"Failed to send to {recipient}")
except Exception as e:
results['failed'] += 1
results['errors'].append(
f"Error sending to {recipient}: {str(e)}"
)
return results
# Example usage and test functions
def test_basic_email():
"""Test sending a basic text email"""
sender = EmailSender()
success = sender.send_text_email(
to_email='test@recipient.local',
subject='Python Test Email',
body='This is a test email sent from Python using MailHog.'
)
print(f"Basic email test: {'PASSED' if success else 'FAILED'}")
def test_html_email():
"""Test sending an HTML email"""
sender = EmailSender()
html_content = '''
<html>
<body>
<h1>HTML Test Email</h1>
<p>This is an <strong>HTML email</strong> sent from Python.</p>
<ul>
<li>Feature 1</li>
<li>Feature 2</li>
<li>Feature 3</li>
</ul>
<p style="color: blue;">Styled text example</p>
</body>
</html>
'''
success = sender.send_html_email(
to_email='html@test.local',
subject='HTML Email Test',
html_body=html_content,
text_body='This is the plain text version of the HTML email.'
)
print(f"HTML email test: {'PASSED' if success else 'FAILED'}")
def test_bulk_emails():
"""Test sending bulk emails"""
sender = EmailSender()
recipients = [
'bulk1@test.local',
'bulk2@test.local',
'bulk3@test.local'
]
results = sender.send_bulk_emails(
recipients=recipients,
subject='Bulk Email Test',
body='This is a bulk email test sent to multiple recipients.'
)
print(
"Bulk email test: " +
f"{results['successful']}/{results['total']} successful"
)
if results['errors']:
print("Errors:", results['errors'])
def test_connection():
"""Test SMTP connection"""
try:
sender = EmailSender()
with sender.create_connection() as server:
server.noop() # No-operation command
print("SMTP connection test: PASSED")
return True
except Exception as e:
print(f"SMTP connection test: FAILED - {e}")
return False
if __name__ == '__main__':
print("Running MailHog Python email tests...")
print("=" * 40)
# Run all tests
test_connection()
test_basic_email()
test_html_email()
test_bulk_emails()
print("=" * 40)
print(
"Email tests completed. Check MailHog UI at http://localhost:8025"
)

View File

@@ -0,0 +1,52 @@
version: '3.8'
services:
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025" # SMTP port
- "8025:8025" # UI/API port
environment:
- MH_HOSTNAME=mailhog.docker.local
- MH_SMTP_BIND_ADDR=0.0.0.0:1025
- MH_UI_BIND_ADDR=0.0.0.0:8025
- MH_API_BIND_ADDR=0.0.0.0:8025
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8025/api/v1/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# Optional: MongoDB for persistent storage
mongodb:
image: mongo:latest
ports:
- "27017:27017"
environment:
- MONGO_INITDB_DATABASE=mailhog
volumes:
- mongodb_data:/data/db
- ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
restart: unless-stopped
# Optional: Application that uses MailHog
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- SMTP_HOST=mailhog
- SMTP_PORT=1025
- MAILHOG_URL=http://mailhog:8025
depends_on:
- mailhog
volumes:
- .:/app
- /app/node_modules
command: npm run dev
volumes:
mongodb_data:

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

View File

@@ -0,0 +1,861 @@
#!/bin/bash
# mailhog_manager.sh - Comprehensive MailHog server management utility
# Usage: ./mailhog_manager.sh [COMMAND] [OPTIONS]
set -e
# Default configuration
DEFAULT_SMTP_PORT=1025
DEFAULT_UI_PORT=8025
DEFAULT_API_PORT=8025
DEFAULT_HOSTNAME="mailhog.local"
DEFAULT_STORAGE="memory"
PID_FILE="/tmp/mailhog.pid"
LOG_FILE="/tmp/mailhog.log"
CONFIG_FILE="$HOME/.mailhog_config"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_header() {
echo -e "${CYAN}=== $1 ===${NC}"
}
# Show usage information
show_usage() {
cat << EOF
MailHog Server Manager
Usage: $0 COMMAND [OPTIONS]
COMMANDS:
start Start MailHog server
stop Stop MailHog server
restart Restart MailHog server
status Show MailHog server status
logs Show MailHog server logs
config Show current configuration
test Test MailHog functionality
cleanup Clean up MailHog data
install Install MailHog (if not installed)
update Update MailHog to latest version
START OPTIONS:
-p, --smtp-port PORT SMTP port (default: $DEFAULT_SMTP_PORT)
-u, --ui-port PORT UI/API port (default: $DEFAULT_UI_PORT)
-h, --hostname HOST Hostname (default: $DEFAULT_HOSTNAME)
-s, --storage TYPE Storage type: memory, mongodb, maildir (default: $DEFAULT_STORAGE)
--mongo-uri URI MongoDB connection URI (for MongoDB storage)
--mongo-db DB MongoDB database name (default: mailhog)
--mongo-coll COLL MongoDB collection name (default: messages)
--maildir-path PATH Maildir storage path
--auth-file FILE Authentication file path
--outgoing-smtp FILE Outgoing SMTP configuration file
--cors-origin ORIGIN CORS origin
--invite-jim Enable Jim network simulation
-d, --daemon Run in daemon mode
-v, --verbose Enable verbose logging
CONFIGURATION:
--save Save current configuration as default
--load Load saved configuration
--reset Reset configuration to defaults
TESTING OPTIONS:
--send-email Send test email
--check-connection Check SMTP connection
--check-api Check API connection
--cleanup-test Clean up test emails
EXAMPLES:
# Start MailHog with default settings
$0 start
# Start with custom ports and MongoDB storage
$0 start --smtp-port 1026 --ui-port 8026 --storage mongodb --mongo-uri mongodb://localhost:27017
# Start in daemon mode with authentication
$0 start --auth-file /path/to/auth.txt --daemon
# Check server status
$0 status
# Send test email
$0 test --send-email --to test@recipient.local
# View logs
$0 logs
# Save current configuration
$0 config --save
INSTALLATION:
# Install MailHog (Linux/macOS)
$0 install
# Update to latest version
$0 update
EOF
}
# Load configuration from file
load_config() {
if [[ -f "$CONFIG_FILE" ]]; then
print_info "Loading configuration from $CONFIG_FILE"
# Source the configuration file
source "$CONFIG_FILE"
fi
}
# Save current configuration
save_config() {
cat > "$CONFIG_FILE" << EOF
# MailHog Configuration
SMTP_PORT="$SMTP_PORT"
UI_PORT="$UI_PORT"
API_PORT="$API_PORT"
HOSTNAME="$HOSTNAME"
STORAGE="$STORAGE"
MONGO_URI="$MONGO_URI"
MONGO_DB="$MONGO_DB"
MONGO_COLL="$MONGO_COLL"
MAILDIR_PATH="$MAILDIR_PATH"
AUTH_FILE="$AUTH_FILE"
OUTGOING_SMTP="$OUTGOING_SMTP"
CORS_ORIGIN="$CORS_ORIGIN"
INVITE_JIM="$INVITE_JIM"
EOF
print_success "Configuration saved to $CONFIG_FILE"
}
# Reset configuration to defaults
reset_config() {
SMTP_PORT="$DEFAULT_SMTP_PORT"
UI_PORT="$DEFAULT_UI_PORT"
API_PORT="$DEFAULT_API_PORT"
HOSTNAME="$DEFAULT_HOSTNAME"
STORAGE="$DEFAULT_STORAGE"
MONGO_URI=""
MONGO_DB="mailhog"
MONGO_COLL="messages"
MAILDIR_PATH=""
AUTH_FILE=""
OUTGOING_SMTP=""
CORS_ORIGIN=""
INVITE_JIM=""
print_success "Configuration reset to defaults"
}
# Check if MailHog is installed
check_mailhog_installed() {
if command -v mailhog >/dev/null 2>&1; then
return 0
else
return 1
fi
}
# Install MailHog
install_mailhog() {
print_header "Installing MailHog"
if check_mailhog_installed; then
print_warning "MailHog is already installed"
return 0
fi
local os=$(uname -s | tr '[:upper:]' '[:lower:]')
local arch=$(uname -m)
# Determine architecture
case "$arch" in
x86_64)
arch="amd64"
;;
aarch64|arm64)
arch="arm64"
;;
armv7l)
arch="arm7"
;;
*)
print_error "Unsupported architecture: $arch"
return 1
;;
esac
# Get latest version
local latest_version=$(curl -s "https://api.github.com/repos/mailhog/MailHog/releases/latest" | grep -o '"tag_name": "[^"]*' | cut -d'"' -f2 | sed 's/v//')
if [[ -z "$latest_version" ]]; then
print_error "Failed to fetch latest MailHog version"
return 1
fi
local download_url="https://github.com/mailhog/MailHog/releases/download/v${latest_version}/MailHog_${os}_${arch}.zip"
print_info "Downloading MailHog v${latest_version} for ${os}/${arch}..."
# Download and extract
local temp_dir=$(mktemp -d)
cd "$temp_dir"
if curl -L -o mailhog.zip "$download_url"; then
unzip mailhog.zip
chmod +x MailHog
# Move to binary directory
if [[ -w "/usr/local/bin" ]]; then
sudo mv MailHog /usr/local/bin/
elif [[ -w "$HOME/.local/bin" ]]; then
mkdir -p "$HOME/.local/bin"
mv MailHog "$HOME/.local/bin/"
# Add to PATH if not already there
if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then
echo 'export PATH="$PATH:$HOME/.local/bin"' >> "$HOME/.bashrc"
export PATH="$PATH:$HOME/.local/bin"
fi
else
print_error "No writable binary directory found. Please install manually."
cd /
rm -rf "$temp_dir"
return 1
fi
cd /
rm -rf "$temp_dir"
print_success "MailHog v${latest_version} installed successfully"
return 0
else
print_error "Failed to download MailHog"
cd /
rm -rf "$temp_dir"
return 1
fi
}
# Update MailHog
update_mailhog() {
print_header "Updating MailHog"
if ! check_mailhog_installed; then
print_warning "MailHog not installed. Installing..."
install_mailhog
return $?
fi
# Stop MailHog if running
if is_mailhog_running; then
stop_mailhog
fi
# Install latest version
install_mailhog
}
# Check if MailHog is running
is_mailhog_running() {
if [[ -f "$PID_FILE" ]]; then
local pid=$(cat "$PID_FILE")
if ps -p "$pid" > /dev/null 2>&1; then
return 0
else
# PID file exists but process is not running
rm -f "$PID_FILE"
return 1
fi
fi
# Check by looking for mailhog process
if pgrep -f "mailhog" > /dev/null; then
return 0
fi
return 1
}
# Build MailHog command with current configuration
build_mailhog_command() {
local cmd="mailhog"
# Basic configuration
cmd+=" -hostname $HOSTNAME"
cmd+=" -smtp-bind-addr 0.0.0.0:$SMTP_PORT"
cmd+=" -ui-bind-addr 0.0.0.0:$UI_PORT"
cmd+=" -api-bind-addr 0.0.0.0:$API_PORT"
# Storage configuration
case "$STORAGE" in
"mongodb")
if [[ -n "$MONGO_URI" ]]; then
cmd+=" -storage mongodb -mongo-uri $MONGO_URI"
else
cmd+=" -storage mongodb -mongo-uri 127.0.0.1:27017"
fi
if [[ -n "$MONGO_DB" ]]; then
cmd+=" -mongo-db $MONGO_DB"
fi
if [[ -n "$MONGO_COLL" ]]; then
cmd+=" -mongo-coll $MONGO_COLL"
fi
;;
"maildir")
if [[ -n "$MAILDIR_PATH" ]]; then
cmd+=" -storage maildir -maildir-path $MAILDIR_PATH"
else
cmd+=" -storage maildir -maildir-path ./maildir"
fi
;;
*)
cmd+=" -storage memory"
;;
esac
# Optional configurations
if [[ -n "$AUTH_FILE" ]]; then
cmd+=" -auth-file $AUTH_FILE"
fi
if [[ -n "$OUTGOING_SMTP" ]]; then
cmd+=" -outgoing-smtp $OUTGOING_SMTP"
fi
if [[ -n "$CORS_ORIGIN" ]]; then
cmd+=" -cors-origin \"$CORS_ORIGIN\""
fi
if [[ "$INVITE_JIM" == "true" ]]; then
cmd+=" -invite-jim"
fi
echo "$cmd"
}
# Start MailHog
start_mailhog() {
print_header "Starting MailHog"
if is_mailhog_running; then
print_warning "MailHog is already running"
return 0
fi
if ! check_mailhog_installed; then
print_error "MailHog is not installed. Run '$0 install' first."
return 1
fi
# Check if ports are available
if netstat -tlnp 2>/dev/null | grep -q ":$SMTP_PORT "; then
print_error "Port $SMTP_PORT is already in use"
return 1
fi
if netstat -tlnp 2>/dev/null | grep -q ":$UI_PORT "; then
print_error "Port $UI_PORT is already in use"
return 1
fi
local mailhog_cmd=$(build_mailhog_command)
print_info "Starting MailHog with the following command:"
print_info "$mailhog_cmd"
if [[ "$DAEMON_MODE" == true ]]; then
# Start in daemon mode
nohup $mailhog_cmd > "$LOG_FILE" 2>&1 &
local pid=$!
echo "$pid" > "$PID_FILE"
print_success "MailHog started in daemon mode (PID: $pid)"
print_info "Logs are being written to: $LOG_FILE"
else
# Start in foreground
$mailhog_cmd 2>&1 | tee "$LOG_FILE" &
local pid=$!
echo "$pid" > "$PID_FILE"
print_success "MailHog started (PID: $pid)"
fi
# Wait a moment and check if it started successfully
sleep 2
if is_mailhog_running; then
print_success "MailHog started successfully"
print_info "SMTP server: localhost:$SMTP_PORT"
print_info "Web UI: http://localhost:$UI_PORT"
print_info "API: http://localhost:$API_PORT/api/v1"
else
print_error "MailHog failed to start"
return 1
fi
}
# Stop MailHog
stop_mailhog() {
print_header "Stopping MailHog"
if ! is_mailhog_running; then
print_warning "MailHog is not running"
return 0
fi
local pid=""
if [[ -f "$PID_FILE" ]]; then
pid=$(cat "$PID_FILE")
else
# Try to find PID by process name
pid=$(pgrep -f "mailhog" | head -1)
fi
if [[ -n "$pid" ]]; then
print_info "Stopping MailHog (PID: $pid)"
kill "$pid"
# Wait for process to stop
local count=0
while ps -p "$pid" > /dev/null 2>&1 && [[ $count -lt 10 ]]; do
sleep 1
((count++))
done
# Force kill if still running
if ps -p "$pid" > /dev/null 2>&1; then
print_warning "Force killing MailHog process"
kill -9 "$pid"
fi
rm -f "$PID_FILE"
print_success "MailHog stopped"
else
print_warning "Could not find MailHog process to stop"
fi
}
# Restart MailHog
restart_mailhog() {
print_header "Restarting MailHog"
stop_mailhog
sleep 2
start_mailhog
}
# Show MailHog status
show_status() {
print_header "MailHog Status"
if is_mailhog_running; then
print_success "MailHog is running"
# Show process information
if [[ -f "$PID_FILE" ]]; then
local pid=$(cat "$PID_FILE")
print_info "PID: $pid"
print_info "Command: $(ps -p "$pid" -o cmd=)"
fi
# Show configuration
echo ""
print_info "Configuration:"
print_info " SMTP Port: $SMTP_PORT"
print_info " UI Port: $UI_PORT"
print_info " API Port: $API_PORT"
print_info " Hostname: $HOSTNAME"
print_info " Storage: $STORAGE"
# Test API connection
echo ""
print_info "Testing API connection..."
if curl -s --max-time 3 "http://localhost:$API_PORT/api/v1/status" > /dev/null 2>&1; then
print_success "API is accessible"
local messages=$(curl -s "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null | jq -r '.total // 0' 2>/dev/null || echo "unknown")
print_info "Messages stored: $messages"
else
print_error "API is not accessible"
fi
# Show URLs
echo ""
print_info "Access URLs:"
print_info " Web UI: http://localhost:$UI_PORT"
print_info " API: http://localhost:$API_PORT/api/v1"
print_info " SMTP: localhost:$SMTP_PORT"
else
print_error "MailHog is not running"
fi
}
# Show logs
show_logs() {
print_header "MailHog Logs"
if [[ -f "$LOG_FILE" ]]; then
tail -f "$LOG_FILE"
else
print_warning "Log file not found: $LOG_FILE"
fi
}
# Show configuration
show_config() {
print_header "Current Configuration"
echo "SMTP Port: $SMTP_PORT"
echo "UI Port: $UI_PORT"
echo "API Port: $API_PORT"
echo "Hostname: $HOSTNAME"
echo "Storage: $STORAGE"
if [[ "$STORAGE" == "mongodb" ]]; then
echo "MongoDB URI: ${MONGO_URI:-"127.0.0.1:27017"}"
echo "MongoDB DB: $MONGO_DB"
echo "MongoDB Collection: $MONGO_COLL"
fi
if [[ "$STORAGE" == "maildir" ]]; then
echo "Maildir Path: ${MAILDIR_PATH:-"./maildir"}"
fi
echo "Auth File: ${AUTH_FILE:-"none"}"
echo "Outgoing SMTP: ${OUTGOING_SMTP:-"none"}"
echo "CORS Origin: ${CORS_ORIGIN:-"none"}"
echo "Jim Simulation: ${INVITE_JIM:-"false"}"
echo ""
echo "MailHog Command:"
echo "$(build_mailhog_command)"
}
# Test MailHog functionality
test_mailhog() {
print_header "Testing MailHog"
if ! is_mailhog_running; then
print_error "MailHog is not running"
return 1
fi
# Test API connection
if [[ "$CHECK_API" == true ]]; then
print_info "Testing API connection..."
local response=$(curl -s "http://localhost:$API_PORT/api/v1/status" 2>/dev/null)
if [[ $? -eq 0 ]]; then
print_success "API connection successful"
echo "$response" | jq . 2>/dev/null || echo "$response"
else
print_error "API connection failed"
fi
fi
# Test SMTP connection
if [[ "$CHECK_CONNECTION" == true ]]; then
print_info "Testing SMTP connection..."
if echo "" | nc -w 3 localhost "$SMTP_PORT" 2>/dev/null | grep -q "220"; then
print_success "SMTP connection successful"
else
print_error "SMTP connection failed"
fi
fi
# Send test email
if [[ "$SEND_EMAIL" == true ]]; then
print_info "Sending test email..."
local test_to="${TEST_TO:-test@recipient.local}"
local test_from="${TEST_FROM:-test@sender.local}"
local test_subject="MailHog Test Email $(date +%s)"
local test_body="This is a test email sent at $(date)"
# Send email using netcat
(
echo "EHLO localhost"
echo "MAIL FROM:<$test_from>"
echo "RCPT TO:<$test_to>"
echo "DATA"
echo "From: $test_from"
echo "To: $test_to"
echo "Subject: $test_subject"
echo "Date: $(date -R)"
echo ""
echo "$test_body"
echo "."
echo "QUIT"
) | nc localhost "$SMTP_PORT" 2>/dev/null
if [[ $? -eq 0 ]]; then
print_success "Test email sent to $test_to"
# Wait and check if received
sleep 2
local messages=$(curl -s "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null | jq -r '.total // 0' 2>/dev/null)
if [[ "$messages" -gt 0 ]]; then
print_success "Test email received by MailHog ($messages total messages)"
else
print_warning "Test email not found in MailHog (may need more time)"
fi
else
print_error "Failed to send test email"
fi
fi
# Clean up test emails
if [[ "$CLEANUP_TEST" == true ]]; then
print_info "Cleaning up test emails..."
local response=$(curl -s -X DELETE "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null)
if [[ $? -eq 0 ]]; then
print_success "Test emails cleaned up"
else
print_warning "Failed to clean up test emails"
fi
fi
}
# Clean up MailHog data
cleanup_mailhog() {
print_header "Cleaning up MailHog"
if is_mailhog_running; then
print_info "Clearing all messages..."
local response=$(curl -s -X DELETE "http://localhost:$API_PORT/api/v1/messages" 2>/dev/null)
if [[ $? -eq 0 ]]; then
print_success "All messages cleared"
else
print_warning "Failed to clear messages"
fi
else
print_warning "MailHog is not running"
fi
# Clean up temporary files
print_info "Cleaning up temporary files..."
rm -f "$PID_FILE" "$LOG_FILE"
# Optional: Clean up maildir if used
if [[ "$STORAGE" == "maildir" ]] && [[ -n "$MAILDIR_PATH" ]]; then
if [[ -d "$MAILDIR_PATH" ]]; then
print_info "Cleaning up Maildir storage..."
rm -rf "$MAILDIR_PATH"/{cur,new,tmp}/*
print_success "Maildir storage cleaned"
fi
fi
print_success "Cleanup completed"
}
# Main function
main() {
# Load default configuration
load_config
# Set defaults if not loaded
SMTP_PORT="${SMTP_PORT:-$DEFAULT_SMTP_PORT}"
UI_PORT="${UI_PORT:-$DEFAULT_UI_PORT}"
API_PORT="${API_PORT:-$DEFAULT_API_PORT}"
HOSTNAME="${HOSTNAME:-$DEFAULT_HOSTNAME}"
STORAGE="${STORAGE:-$DEFAULT_STORAGE}"
MONGO_DB="${MONGO_DB:-mailhog}"
MONGO_COLL="${MONGO_COLL:-messages}"
DAEMON_MODE=false
# Parse command line arguments
case "${1:-}" in
"start")
shift
while [[ $# -gt 0 ]]; do
case $1 in
-p|--smtp-port)
SMTP_PORT="$2"
shift 2
;;
-u|--ui-port)
UI_PORT="$2"
API_PORT="$2"
shift 2
;;
-h|--hostname)
HOSTNAME="$2"
shift 2
;;
-s|--storage)
STORAGE="$2"
shift 2
;;
--mongo-uri)
MONGO_URI="$2"
shift 2
;;
--mongo-db)
MONGO_DB="$2"
shift 2
;;
--mongo-coll)
MONGO_COLL="$2"
shift 2
;;
--maildir-path)
MAILDIR_PATH="$2"
shift 2
;;
--auth-file)
AUTH_FILE="$2"
shift 2
;;
--outgoing-smtp)
OUTGOING_SMTP="$2"
shift 2
;;
--cors-origin)
CORS_ORIGIN="$2"
shift 2
;;
--invite-jim)
INVITE_JIM="true"
shift
;;
-d|--daemon)
DAEMON_MODE=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
--save)
save_config
exit 0
;;
*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
start_mailhog
;;
"stop")
stop_mailhog
;;
"restart")
restart_mailhog
;;
"status")
show_status
;;
"logs")
show_logs
;;
"config")
case "${2:-}" in
"--save")
save_config
;;
"--load")
load_config
print_success "Configuration loaded"
;;
"--reset")
reset_config
;;
*)
show_config
;;
esac
;;
"test")
shift
while [[ $# -gt 0 ]]; do
case $1 in
--send-email)
SEND_EMAIL=true
shift
;;
--check-connection)
CHECK_CONNECTION=true
shift
;;
--check-api)
CHECK_API=true
shift
;;
--cleanup-test)
CLEANUP_TEST=true
shift
;;
--to)
TEST_TO="$2"
shift 2
;;
--from)
TEST_FROM="$2"
shift 2
;;
*)
print_error "Unknown test option: $1"
show_usage
exit 1
;;
esac
done
# If no specific test options, run all tests
if [[ -z "$SEND_EMAIL" && -z "$CHECK_CONNECTION" && -z "$CHECK_API" && -z "$CLEANUP_TEST" ]]; then
SEND_EMAIL=true
CHECK_CONNECTION=true
CHECK_API=true
CLEANUP_TEST=true
fi
test_mailhog
;;
"cleanup")
cleanup_mailhog
;;
"install")
install_mailhog
;;
"update")
update_mailhog
;;
"help"|"-h"|"--help")
show_usage
;;
*)
print_error "Unknown command: ${1:-}"
show_usage
exit 1
;;
esac
}
# Run main function
main "$@"

View File

@@ -0,0 +1,493 @@
#!/bin/bash
# send_test_email.sh - Send test emails via SMTP to MailHog
# Usage: ./send_test_email.sh [OPTIONS]
set -e
# Default values
SMTP_HOST="localhost"
SMTP_PORT="1025"
TO="test@recipient.local"
FROM="test@sender.local"
SUBJECT="Test Email from MailHog Skill"
BODY="This is a test email sent via the MailHog skill script."
HTML=false
ATTACHMENT=""
VERBOSE=false
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Show usage information
show_usage() {
cat << EOF
Send Test Email Script
Usage: $0 [OPTIONS]
OPTIONS:
-h, --help Show this help message
-t, --to EMAIL Recipient email address (default: test@recipient.local)
-f, --from EMAIL Sender email address (default: test@sender.local)
-s, --subject SUBJECT Email subject (default: "Test Email from MailHog Skill")
-b, --body TEXT Email body text (default: simple test message)
--html Send as HTML email
--attachment FILE Attach file to email
--smtp-host HOST SMTP server host (default: localhost)
--smtp-port PORT SMTP server port (default: 1025)
-v, --verbose Enable verbose output
--template TEMPLATE Use predefined template (welcome, notification, reset)
--multiple COUNT Send multiple emails (for testing)
TEMPLATES:
welcome - Welcome email template
notification - Notification email template
reset - Password reset email template
bulk - Bulk email test template
EXAMPLES:
# Send simple test email
$0 --to user@test.local --subject "Welcome!"
# Send HTML email with template
$0 --html --template welcome --to newuser@test.local
# Send multiple emails for load testing
$0 --multiple 100 --subject "Load Test"
# Send email with attachment
$0 --attachment ./test.pdf --to recipient@test.local
EOF
}
# Email templates
get_template_content() {
local template="$1"
local to="$2"
case "$template" in
"welcome")
echo "<h1>Welcome to Our Service!</h1>
<p>Hello $to,</p>
<p>Thank you for signing up for our service. We're excited to have you on board!</p>
<p>Here's what you can do next:</p>
<ul>
<li>Complete your profile</li>
<li>Explore our features</li>
<li>Connect with other users</li>
</ul>
<p>Best regards,<br>The Team</p>"
echo "Welcome to Our Service!"
;;
"notification")
echo "<h2>Important Notification</h1>
<p>Hello $to,</p>
<p>You have an important notification that requires your attention.</p>
<div style='background-color: #f8f9fa; padding: 15px; border-left: 4px solid #007bff;'>
<strong>Action Required:</strong> Please review your account settings.
</div>
<p>Thank you for your prompt attention to this matter.</p>"
echo "Important Notification"
;;
"reset")
echo "<h1>Password Reset Request</h1>
<p>Hello $to,</p>
<p>You requested a password reset for your account. Click the link below to reset your password:</p>
<p><a href='https://example.com/reset?token=abc123' style='background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;'>Reset Password</a></p>
<p>If you didn't request this reset, please ignore this email. The link will expire in 1 hour.</p>
<p>Best regards,<br>Support Team</p>"
echo "Password Reset Request"
;;
"bulk")
echo "<h2>Bulk Communication</h1>
<p>This is a test email for bulk communication testing.</p>
<p>Message ID: $(date +%s)</p>
<p>Timestamp: $(date)</p>"
echo "Bulk Communication Test"
;;
*)
print_error "Unknown template: $template"
exit 1
;;
esac
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-t|--to)
TO="$2"
shift 2
;;
-f|--from)
FROM="$2"
shift 2
;;
-s|--subject)
SUBJECT="$2"
shift 2
;;
-b|--body)
BODY="$2"
shift 2
;;
--html)
HTML=true
shift
;;
--attachment)
ATTACHMENT="$2"
if [[ ! -f "$ATTACHMENT" ]]; then
print_error "Attachment file not found: $ATTACHMENT"
exit 1
fi
shift 2
;;
--smtp-host)
SMTP_HOST="$2"
shift 2
;;
--smtp-port)
SMTP_PORT="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
--template)
TEMPLATE="$2"
shift 2
;;
--multiple)
MULTIPLE="$2"
shift 2
;;
*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Check if netcat is available
check_dependencies() {
local missing_deps=()
if ! command -v nc >/dev/null 2>&1 && ! command -v telnet >/dev/null 2>&1; then
missing_deps+=("nc or telnet")
fi
if [[ ${#missing_deps[@]} -gt 0 ]]; then
print_error "Missing required dependencies: ${missing_deps[*]}"
print_info "Install missing dependencies and try again."
exit 1
fi
}
# Test SMTP connection
test_smtp_connection() {
print_info "Testing SMTP connection to $SMTP_HOST:$SMTP_PORT..."
if command -v nc >/dev/null 2>&1; then
if echo "" | nc -w 5 "$SMTP_HOST" "$SMTP_PORT" >/dev/null 2>&1; then
print_success "SMTP connection successful"
return 0
else
print_error "Failed to connect to SMTP server"
return 1
fi
elif command -v telnet >/dev/null 2>&1; then
timeout 5 telnet "$SMTP_HOST" "$SMTP_PORT" </dev/null >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
print_success "SMTP connection successful"
return 0
else
print_error "Failed to connect to SMTP server"
return 1
fi
fi
}
# Create temporary file for email
create_email_file() {
local temp_file=$(mktemp)
local boundary="MAILHOG-$(date +%s%N)"
cat << EOF > "$temp_file"
From: $FROM
To: $TO
Subject: $SUBJECT
Date: $(date -R)
MIME-Version: 1.0
EOF
if [[ -n "$ATTACHMENT" ]]; then
echo "Content-Type: multipart/mixed; boundary=\"$boundary\"" >> "$temp_file"
echo "" >> "$temp_file"
echo "--$boundary" >> "$temp_file"
if [[ "$HTML" == true ]]; then
echo "Content-Type: text/html; charset=UTF-8" >> "$temp_file"
echo "Content-Transfer-Encoding: 7bit" >> "$temp_file"
else
echo "Content-Type: text/plain; charset=UTF-8" >> "$temp_file"
echo "Content-Transfer-Encoding: 7bit" >> "$temp_file"
fi
echo "" >> "$temp_file"
echo "$BODY" >> "$temp_file"
echo "" >> "$temp_file"
echo "--$boundary" >> "$temp_file"
# Add attachment
local attachment_name=$(basename "$ATTACHMENT")
echo "Content-Type: application/octet-stream" >> "$temp_file"
echo "Content-Transfer-Encoding: base64" >> "$temp_file"
echo "Content-Disposition: attachment; filename=\"$attachment_name\"" >> "$temp_file"
echo "" >> "$temp_file"
base64 "$ATTACHMENT" >> "$temp_file"
echo "" >> "$temp_file"
echo "--$boundary--" >> "$temp_file"
else
if [[ "$HTML" == true ]]; then
echo "Content-Type: text/html; charset=UTF-8" >> "$temp_file"
echo "Content-Transfer-Encoding: 7bit" >> "$temp_file"
else
echo "Content-Type: text/plain; charset=UTF-8" >> "$temp_file"
fi
echo "" >> "$temp_file"
echo "$BODY" >> "$temp_file"
fi
echo "$temp_file"
}
# Send email using SMTP
send_email_smtp() {
local email_file="$1"
if [[ "$VERBOSE" == true ]]; then
print_info "Sending email with the following content:"
cat "$email_file"
echo ""
fi
# Connect to SMTP and send email
(
echo "EHLO mailhog-test.local"
echo "MAIL FROM:<$FROM>"
echo "RCPT TO:<$TO>"
echo "DATA"
cat "$email_file"
echo "."
echo "QUIT"
) | nc "$SMTP_HOST" "$SMTP_PORT" 2>/dev/null
if [[ $? -eq 0 ]]; then
print_success "Email sent successfully"
return 0
else
print_error "Failed to send email"
return 1
fi
}
# Send email using Python if available
send_email_python() {
local email_file="$1"
if command -v python3 >/dev/null 2>&1; then
python3 << EOF
import smtplib
import ssl
from email import message_from_file
try:
with open('$email_file', 'r') as f:
msg = message_from_file(f)
# Convert to string for sending
email_string = msg.as_string()
# Connect to SMTP server
server = smtplib.SMTP('$SMTP_HOST', $SMTP_PORT)
server.set_debuglevel(1 if '$VERBOSE' == 'true' else 0)
# Send email
server.sendmail('$FROM', ['$TO'], email_string)
server.quit()
print("Email sent successfully via Python")
except Exception as e:
print(f"Failed to send email via Python: {e}")
exit(1)
EOF
return $?
else
return 1
fi
}
# Send multiple emails for testing
send_multiple_emails() {
local count="$1"
local success_count=0
local failed_count=0
print_info "Sending $count emails..."
for ((i=1; i<=count; i++)); do
local temp_subject="$SUBJECT - #$i"
local temp_body="This is test email #$i\n\nSent at: $(date)"
if [[ "$HTML" == true ]]; then
temp_body="<h2>This is test email #$i</h2><p>Sent at: $(date)</p>"
fi
local temp_file=$(mktemp)
cat << EOF > "$temp_file"
From: $FROM
To: $TO
Subject: $temp_subject
Date: $(date -R)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
$temp_body
EOF
if send_email_smtp "$temp_file"; then
((success_count++))
else
((failed_count++))
fi
rm -f "$temp_file"
# Progress indicator
if [[ $((i % 10)) -eq 0 ]]; then
print_info "Sent $i/$count emails..."
fi
# Small delay to avoid overwhelming the server
sleep 0.1
done
print_success "Email sending complete: $success_count successful, $failed_count failed"
}
# Check MailHog for the sent email
verify_email_received() {
local max_attempts=10
local attempt=1
print_info "Verifying email was received by MailHog..."
while [[ $attempt -le $max_attempts ]]; do
local response=$(curl -s "http://localhost:8025/api/v1/messages?limit=1" 2>/dev/null)
if [[ $? -eq 0 ]] && echo "$response" | grep -q "\"total\":1"; then
print_success "Email verified in MailHog"
if [[ "$VERBOSE" == true ]]; then
echo "$response" | jq '.'
fi
return 0
fi
sleep 1
((attempt++))
done
print_warning "Email not found in MailHog after verification attempts"
return 1
}
# Main execution
main() {
print_info "MailHog Test Email Sender"
# Check dependencies
check_dependencies
# Apply template if specified
if [[ -n "$TEMPLATE" ]]; then
if [[ "$VERBOSE" == true ]]; then
print_info "Using template: $TEMPLATE"
fi
local template_result=$(get_template_content "$TEMPLATE" "$TO")
BODY=$(echo "$template_result" | sed -n '1p')
SUBJECT=$(echo "$template_result" | sed -n '2p')
HTML=true
fi
# Test SMTP connection
test_smtp_connection
# Send multiple emails if requested
if [[ -n "$MULTIPLE" ]]; then
send_multiple_emails "$MULTIPLE"
exit 0
fi
# Create email file
local email_file=$(create_email_file)
# Send email
print_info "Sending email to $TO from $FROM via $SMTP_HOST:$SMTP_PORT..."
if ! send_email_smtp "$email_file"; then
# Fallback to Python if netcat fails
if send_email_python "$email_file"; then
print_success "Email sent successfully via Python fallback"
else
print_error "All email sending methods failed"
rm -f "$email_file"
exit 1
fi
fi
# Clean up
rm -f "$email_file"
# Verify email was received (only if running on same host as MailHog)
if [[ "$SMTP_HOST" == "localhost" ]] || [[ "$SMTP_HOST" == "127.0.0.1" ]]; then
verify_email_received
fi
print_success "Test email sending completed successfully"
}
# Run main function
main "$@"

View File

@@ -0,0 +1,855 @@
#!/bin/bash
# test_email_workflow.sh - Automated email testing workflow
# Usage: ./test_email_workflow.sh [OPTIONS]
set -e
# Default values
MAILHOG_URL="http://localhost:8025"
SMTP_HOST="localhost"
SMTP_PORT="1025"
CONFIG_FILE=""
TEST_SCENARIO="basic"
CLEANUP=true
VERBOSE=false
TIMEOUT=30
OUTPUT_FORMAT="text"
REPORT_FILE="email_test_report.txt"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Global variables
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
TEST_RESULTS=()
# Print colored output
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
[[ "$VERBOSE" == true ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1" >> "$REPORT_FILE"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
[[ "$VERBOSE" == true ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$REPORT_FILE"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
[[ "$VERBOSE" == true ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1" >> "$REPORT_FILE"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
[[ "$VERBOSE" == true ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$REPORT_FILE"
}
print_test() {
echo -e "${CYAN}[TEST]${NC} $1"
[[ "$VERBOSE" == true ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] TEST: $1" >> "$REPORT_FILE"
}
# Show usage information
show_usage() {
cat << EOF
Automated Email Testing Workflow
Usage: $0 [OPTIONS]
OPTIONS:
-h, --help Show this help message
-c, --config FILE Load test configuration from file
-s, --scenario SCENARIO Test scenario to run (default: basic)
--url URL MailHog API URL (default: http://localhost:8025)
--smtp-host HOST SMTP server host (default: localhost)
--smtp-port PORT SMTP server port (default: 1025)
-t, --timeout SECONDS Timeout for each test (default: 30)
-v, --verbose Enable verbose output
--no-cleanup Don't clean up test emails after testing
--output FORMAT Report format: text, json, csv (default: text)
--report-file FILE Save report to file (default: email_test_report.txt)
TEST SCENARIOS:
basic - Basic email sending and receiving tests
html - HTML email formatting tests
attachments - Email attachment tests
bulk - Bulk email sending tests
performance - Performance and load tests
integration - Integration testing with different configurations
CONFIGURATION FILE FORMAT (JSON):
{
"tests": [
{
"name": "Test Name",
"type": "send|receive|search",
"to": "recipient@test.local",
"from": "sender@test.local",
"subject": "Test Subject",
"body": "Test body",
"html": false,
"expected_count": 1,
"timeout": 30
}
]
}
EXAMPLES:
# Run basic email tests
$0 --scenario basic
# Run HTML email tests with verbose output
$0 --scenario html --verbose
# Run tests with custom configuration
$0 --config test_config.json --timeout 60
# Run performance tests and save JSON report
$0 --scenario performance --output json --report-file perf_results.json
EOF
}
# Initialize test environment
init_test_environment() {
print_info "Initializing email test environment..."
# Initialize report file
if [[ "$VERBOSE" == true ]]; then
echo "Email Test Report - $(date)" > "$REPORT_FILE"
echo "==============================" >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
fi
# Check if MailHog is accessible
check_mailhog_connection
# Clear existing messages if cleanup is enabled
if [[ "$CLEANUP" == true ]]; then
clear_mailhog_messages
fi
# Check SMTP connection
check_smtp_connection
print_success "Test environment initialized successfully"
}
# Check MailHog connection
check_mailhog_connection() {
print_test "Checking MailHog API connection..."
local response=$(curl -s --max-time 5 "$MAILHOG_URL/api/v1/status" 2>/dev/null)
if [[ $? -eq 0 ]] && echo "$response" | grep -q "messages"; then
print_success "MailHog API is accessible"
return 0
else
print_error "Cannot connect to MailHog API at $MAILHOG_URL"
return 1
fi
}
# Check SMTP connection
check_smtp_connection() {
print_test "Checking SMTP connection to $SMTP_HOST:$SMTP_PORT..."
if command -v nc >/dev/null 2>&1; then
if echo "" | nc -w 5 "$SMTP_HOST" "$SMTP_PORT" >/dev/null 2>&1; then
print_success "SMTP connection successful"
return 0
fi
elif command -v telnet >/dev/null 2>&1; then
timeout 5 telnet "$SMTP_HOST" "$SMTP_PORT" </dev/null >/dev/null 2>&1
if [[ $? -eq 0 || $? -eq 124 ]]; then
print_success "SMTP connection successful"
return 0
fi
fi
print_error "Failed to connect to SMTP server"
return 1
}
# Clear all MailHog messages
clear_mailhog_messages() {
print_test "Clearing existing MailHog messages..."
local response=$(curl -s -X DELETE "$MAILHOG_URL/api/v1/messages" 2>/dev/null)
if [[ $? -eq 0 ]]; then
print_success "MailHog messages cleared"
else
print_warning "Failed to clear MailHog messages"
fi
}
# Send test email
send_test_email() {
local to="$1"
local from="$2"
local subject="$3"
local body="$4"
local html="${5:-false}"
print_test "Sending test email to $to..."
# Create email content
local email_content="From: $from
To: $to
Subject: $subject
Date: $(date -R)
MIME-Version: 1.0"
if [[ "$html" == true ]]; then
email_content="$email_content
Content-Type: text/html; charset=UTF-8
$body"
else
email_content="$email_content
Content-Type: text/plain; charset=UTF-8
$body"
fi
# Send email via SMTP
(
echo "EHLO mailhog-test.local"
echo "MAIL FROM:<$from>"
echo "RCPT TO:<$to>"
echo "DATA"
echo "$email_content"
echo "."
echo "QUIT"
) | nc "$SMTP_HOST" "$SMTP_PORT" 2>/dev/null
return $?
}
# Wait for email to be received
wait_for_email() {
local to="$1"
local subject="$2"
local timeout="$3"
local start_time=$(date +%s)
local end_time=$((start_time + timeout))
print_test "Waiting for email to be received (timeout: ${timeout}s)..."
while [[ $(date +%s) -lt $end_time ]]; do
local response=$(curl -s "$MAILHOG_URL/api/v1/messages" 2>/dev/null)
if [[ $? -eq 0 ]]; then
local message_count=$(echo "$response" | jq -r '.total // 0' 2>/dev/null)
if [[ "$message_count" -gt 0 ]]; then
# Search for specific email
local search_response=$(curl -s -X POST "$MAILHOG_URL/api/v1/search" \
-H "Content-Type: application/json" \
-d "{\"query\":\"to:$to subject:$subject\"}" 2>/dev/null)
if [[ $? -eq 0 ]]; then
local found_count=$(echo "$search_response" | jq -r '.total // 0' 2>/dev/null)
if [[ "$found_count" -gt 0 ]]; then
print_success "Email received and found"
return 0
fi
fi
# If search fails, check if any new email was received
if [[ "$message_count" -gt 0 ]]; then
print_success "Email received (search failed, but count > 0)"
return 0
fi
fi
fi
sleep 1
done
print_error "Email not received within timeout period"
return 1
}
# Run a single test
run_test() {
local test_name="$1"
local test_type="$2"
shift 2
local test_params=("$@")
print_test "Running test: $test_name"
((TOTAL_TESTS++))
case "$test_type" in
"send")
run_send_test "$test_name" "${test_params[@]}"
;;
"receive")
run_receive_test "$test_name" "${test_params[@]}"
;;
"search")
run_search_test "$test_name" "${test_params[@]}"
;;
"bulk")
run_bulk_test "$test_name" "${test_params[@]}"
;;
*)
print_error "Unknown test type: $test_type"
record_test_result "$test_name" "FAILED" "Unknown test type"
;;
esac
}
# Run send email test
run_send_test() {
local test_name="$1"
local to="$2"
local from="$3"
local subject="$4"
local body="$5"
local html="${6:-false}"
local timeout="${7:-30}"
print_test "Sending email test: $test_name"
# Send email
if send_test_email "$to" "$from" "$subject" "$body" "$html"; then
# Wait for email to be received
if wait_for_email "$to" "$subject" "$timeout"; then
print_success "Test passed: $test_name"
record_test_result "$test_name" "PASSED" "Email sent and received successfully"
((PASSED_TESTS++))
else
print_error "Test failed: $test_name - Email not received"
record_test_result "$test_name" "FAILED" "Email sent but not received"
((FAILED_TESTS++))
fi
else
print_error "Test failed: $test_name - Email sending failed"
record_test_result "$test_name" "FAILED" "Email sending failed"
((FAILED_TESTS++))
fi
}
# Run receive test
run_receive_test() {
local test_name="$1"
local timeout="$2"
print_test "Receive test: $test_name"
# Send a test email first
local test_email="test-$(date +%s)@receiver.local"
if send_test_email "$test_email" "sender@test.local" "Receive Test" "Test email for receive test"; then
if wait_for_email "$test_email" "Receive Test" "$timeout"; then
print_success "Test passed: $test_name"
record_test_result "$test_name" "PASSED" "Email received successfully"
((PASSED_TESTS++))
else
print_error "Test failed: $test_name - Email not received"
record_test_result "$test_name" "FAILED" "Email not received within timeout"
((FAILED_TESTS++))
fi
else
print_error "Test failed: $test_name - Test email sending failed"
record_test_result "$test_name" "FAILED" "Test email sending failed"
((FAILED_TESTS++))
fi
}
# Run search test
run_search_test() {
local test_name="$1"
local query="$2"
local expected_count="$3"
print_test "Search test: $test_name"
# Send a test email first
local test_email="search-test-$(date +%s)@search.local"
if send_test_email "$test_email" "sender@test.local" "Search Test" "Test email for search test"; then
sleep 2 # Wait for email to be indexed
# Search for the email
local search_response=$(curl -s -X POST "$MAILHOG_URL/api/v1/search" \
-H "Content-Type: application/json" \
-d "{\"query\":\"$query\"}" 2>/dev/null)
if [[ $? -eq 0 ]]; then
local found_count=$(echo "$search_response" | jq -r '.total // 0' 2>/dev/null)
if [[ "$found_count" -eq "$expected_count" ]]; then
print_success "Test passed: $test_name - Found $found_count emails"
record_test_result "$test_name" "PASSED" "Found expected $expected_count emails"
((PASSED_TESTS++))
else
print_error "Test failed: $test_name - Expected $expected_count, found $found_count"
record_test_result "$test_name" "FAILED" "Expected $expected_count emails, found $found_count"
((FAILED_TESTS++))
fi
else
print_error "Test failed: $test_name - Search request failed"
record_test_result "$test_name" "FAILED" "Search request failed"
((FAILED_TESTS++))
fi
else
print_error "Test failed: $test_name - Test email sending failed"
record_test_result "$test_name" "FAILED" "Test email sending failed"
((FAILED_TESTS++))
fi
}
# Run bulk test
run_bulk_test() {
local test_name="$1"
local email_count="$2"
local timeout="$3"
print_test "Bulk test: $test_name - Sending $email_count emails"
local sent_count=0
local start_time=$(date +%s)
for ((i=1; i<=email_count; i++)); do
local test_email="bulk-$i-$(date +%s)@bulk.local"
if send_test_email "$test_email" "bulk@sender.local" "Bulk Test #$i" "Bulk test email #$i"; then
((sent_count++))
fi
# Small delay to avoid overwhelming
sleep 0.1
done
local send_time=$(($(date +%s) - start_time))
# Wait for all emails to be received
sleep "$timeout"
# Check total message count
local response=$(curl -s "$MAILHOG_URL/api/v1/messages" 2>/dev/null)
local received_count=0
if [[ $? -eq 0 ]]; then
received_count=$(echo "$response" | jq -r '.total // 0' 2>/dev/null)
fi
if [[ "$sent_count" -eq "$email_count" && "$received_count" -ge "$email_count" ]]; then
print_success "Test passed: $test_name - Sent $sent_count/$email_count, Received $received_count"
record_test_result "$test_name" "PASSED" "Bulk test: Sent $sent_count/$email_count emails in ${send_time}s"
((PASSED_TESTS++))
else
print_error "Test failed: $test_name - Sent $sent_count/$email_count, Received $received_count"
record_test_result "$test_name" "FAILED" "Bulk test: Expected $email_count, sent $sent_count, received $received_count"
((FAILED_TESTS++))
fi
}
# Record test result
record_test_result() {
local test_name="$1"
local result="$2"
local details="$3"
TEST_RESULTS+=("{\"name\":\"$test_name\",\"result\":\"$result\",\"details\":\"$details\"}")
}
# Run predefined test scenarios
run_basic_scenario() {
print_info "Running basic email tests..."
# Test 1: Simple text email
run_test "Simple Text Email" "send" \
"basic-test@test.local" \
"sender@test.local" \
"Basic Test Email" \
"This is a basic test email from the email testing workflow." \
false \
15
# Test 2: Different sender and recipient
run_test "Different Sender/Recipient" "send" \
"recipient1@test.local" \
"sender1@test.local" \
"Different Addresses Test" \
"Testing different sender and recipient addresses." \
false \
15
# Test 3: Special characters in subject
run_test "Special Characters Subject" "send" \
"special@test.local" \
"sender@test.local" \
"Subject with Special chars: àáâãäåæçèéêë" \
"Testing special characters in email subject line." \
false \
15
}
run_html_scenario() {
print_info "Running HTML email tests..."
# Test 1: Basic HTML email
run_test "Basic HTML Email" "send" \
"html@test.local" \
"sender@test.local" \
"HTML Test Email" \
"<h1>HTML Test</h1><p>This is an <strong>HTML</strong> email test.</p><ul><li>Item 1</li><li>Item 2</li></ul>" \
true \
15
# Test 2: HTML with CSS styling
run_test "HTML with CSS" "send" \
"html-css@test.local" \
"sender@test.local" \
"HTML + CSS Test" \
"<html><head><style>body{font-family:Arial,sans-serif} .highlight{background-color:yellow}</style></head><body><h1 class='highlight'>Styled HTML</h1><p>This email includes <span class='highlight'>CSS styling</span>.</p></body></html>" \
true \
15
# Test 3: HTML with images
run_test "HTML with Images" "send" \
"html-img@test.local" \
"sender@test.local" \
"HTML Image Test" \
"<h1>Image Test</h1><p>This email includes an embedded image:</p><img src='cid:test-image' alt='Test Image'><p>If you see an image placeholder, HTML parsing is working.</p>" \
true \
15
}
run_attachments_scenario() {
print_info "Running attachment tests..."
# Note: This is a simplified test. Real attachment testing would require multipart MIME construction
print_warning "Attachment tests require more complex MIME construction. This is a placeholder test."
run_test "Simple Attachment Test" "send" \
"attachment@test.local" \
"sender@test.local" \
"Attachment Test" \
"This test would normally include an attachment, but requires more complex MIME construction." \
false \
15
}
run_bulk_scenario() {
print_info "Running bulk email tests..."
# Test sending multiple emails
run_test "Bulk Send Test" "bulk" 10 30
# Test with more emails
run_test "Bulk Send Large Test" "bulk" 50 60
}
run_performance_scenario() {
print_info "Running performance tests..."
# Test performance with larger volume
run_test "Performance Test" "bulk" 100 120
# Test with timeout constraints
run_test "Quick Response Test" "send" \
"perf@test.local" \
"sender@test.local" \
"Performance Test" \
"Quick performance test email." \
false \
5
}
run_integration_scenario() {
print_info "Running integration tests..."
# Test with various timeouts
run_test "Long Timeout Test" "send" \
"integration@test.local" \
"sender@test.local" \
"Integration Test" \
"Integration test with extended timeout." \
false \
60
# Test search functionality
run_test "Email Search Test" "search" \
"search-test@test.local" \
1
# Test receive functionality
run_test "Receive Test" "receive" \
30
}
# Load configuration from file
load_configuration() {
local config_file="$1"
if [[ ! -f "$config_file" ]]; then
print_error "Configuration file not found: $config_file"
exit 1
fi
print_info "Loading configuration from $config_file"
if command -v jq >/dev/null 2>&1; then
local tests=$(jq -c '.tests[]' "$config_file" 2>/dev/null)
if [[ $? -eq 0 ]]; then
echo "$tests" | while read -r test; do
local name=$(echo "$test" | jq -r '.name // "Unnamed Test"')
local type=$(echo "$test" | jq -r '.type // "send"')
local to=$(echo "$test" | jq -r '.to // "test@local"')
local from=$(echo "$test" | jq -r '.from // "sender@local"')
local subject=$(echo "$test" | jq -r '.subject // "Test Email"')
local body=$(echo "$test" | jq -r '.body // "Test body"')
local html=$(echo "$test" | jq -r '.html // false')
local timeout=$(echo "$test" | jq -r '.timeout // 30')
run_test "$name" "$type" "$to" "$from" "$subject" "$body" "$html" "$timeout"
done
else
print_error "Invalid JSON in configuration file"
exit 1
fi
else
print_error "jq is required for JSON configuration files"
exit 1
fi
}
# Generate test report
generate_report() {
print_info "Generating test report..."
echo ""
echo "=========================================="
echo " EMAIL TEST REPORT "
echo "=========================================="
echo "Total Tests: $TOTAL_TESTS"
echo "Passed Tests: $PASSED_TESTS"
echo "Failed Tests: $FAILED_TESTS"
if [[ $TOTAL_TESTS -gt 0 ]]; then
local success_rate=$(( (PASSED_TESTS * 100) / TOTAL_TESTS ))
echo "Success Rate: ${success_rate}%"
fi
echo "Duration: $((SECONDS)) seconds"
echo "=========================================="
# Save detailed report if verbose mode
if [[ "$VERBOSE" == true ]]; then
echo "" >> "$REPORT_FILE"
echo "Test Summary:" >> "$REPORT_FILE"
echo "Total Tests: $TOTAL_TESTS" >> "$REPORT_FILE"
echo "Passed Tests: $PASSED_TESTS" >> "$REPORT_FILE"
echo "Failed Tests: $FAILED_TESTS" >> "$REPORT_FILE"
if [[ $TOTAL_TESTS -gt 0 ]]; then
local success_rate=$(( (PASSED_TESTS * 100) / TOTAL_TESTS ))
echo "Success Rate: ${success_rate}%" >> "$REPORT_FILE"
fi
echo "Duration: ${SECONDS} seconds" >> "$REPORT_FILE"
fi
# Generate JSON report if requested
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
generate_json_report
elif [[ "$OUTPUT_FORMAT" == "csv" ]]; then
generate_csv_report
fi
}
# Generate JSON report
generate_json_report() {
local json_report_file="${REPORT_FILE%.*}.json"
local json="{\"timestamp\":\"$(date -Iseconds)\",\"total\":$TOTAL_TESTS,\"passed\":$PASSED_TESTS,\"failed\":$FAILED_TESTS,\"duration\":$SECONDS,\"tests\":["
local first=true
for result in "${TEST_RESULTS[@]}"; do
if [[ "$first" == true ]]; then
first=false
else
json+=","
fi
json+="$result"
done
json+="]}"
echo "$json" > "$json_report_file"
print_info "JSON report saved to: $json_report_file"
}
# Generate CSV report
generate_csv_report() {
local csv_report_file="${REPORT_FILE%.*}.csv"
echo "Test Name,Result,Details,Timestamp" > "$csv_report_file"
for result in "${TEST_RESULTS[@]}"; do
local name=$(echo "$result" | jq -r '.name' 2>/dev/null || echo "Unknown")
local res=$(echo "$result" | jq -r '.result' 2>/dev/null || echo "UNKNOWN")
local details=$(echo "$result" | jq -r '.details' 2>/dev/null || echo "No details")
local timestamp=$(date -Iseconds)
echo "\"$name\",\"$res\",\"$details\",\"$timestamp\"" >> "$csv_report_file"
done
print_info "CSV report saved to: $csv_report_file"
}
# Clean up test environment
cleanup_test_environment() {
if [[ "$CLEANUP" == true ]]; then
print_info "Cleaning up test environment..."
clear_mailhog_messages
fi
# Remove temporary files
rm -f /tmp/email_test_*
}
# Main execution
main() {
local start_time=$(date +%s)
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-c|--config)
CONFIG_FILE="$2"
shift 2
;;
-s|--scenario)
TEST_SCENARIO="$2"
shift 2
;;
--url)
MAILHOG_URL="$2"
shift 2
;;
--smtp-host)
SMTP_HOST="$2"
shift 2
;;
--smtp-port)
SMTP_PORT="$2"
shift 2
;;
-t|--timeout)
TIMEOUT="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
--no-cleanup)
CLEANUP=false
shift
;;
--output)
OUTPUT_FORMAT="$2"
shift 2
;;
--report-file)
REPORT_FILE="$2"
shift 2
;;
*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Validate output format
case "$OUTPUT_FORMAT" in
"text"|"json"|"csv")
;;
*)
print_error "Invalid output format: $OUTPUT_FORMAT. Use text, json, or csv."
exit 1
;;
esac
print_info "Starting email testing workflow..."
print_info "Scenario: $TEST_SCENARIO"
print_info "MailHog URL: $MAILHOG_URL"
print_info "SMTP Server: $SMTP_HOST:$SMTP_PORT"
# Initialize test environment
init_test_environment
# Run tests based on scenario or configuration
if [[ -n "$CONFIG_FILE" ]]; then
load_configuration "$CONFIG_FILE"
else
case "$TEST_SCENARIO" in
"basic")
run_basic_scenario
;;
"html")
run_html_scenario
;;
"attachments")
run_attachments_scenario
;;
"bulk")
run_bulk_scenario
;;
"performance")
run_performance_scenario
;;
"integration")
run_integration_scenario
;;
*)
print_error "Unknown test scenario: $TEST_SCENARIO"
show_usage
exit 1
;;
esac
fi
# Generate report
generate_report
# Clean up
cleanup_test_environment
# Exit with appropriate code
local end_time=$(date +%s)
local duration=$((end_time - start_time))
print_info "Test workflow completed in ${duration} seconds"
if [[ $FAILED_TESTS -eq 0 ]]; then
print_success "All tests passed!"
exit 0
else
print_error "$FAILED_TESTS test(s) failed"
exit 1
fi
}
# Run main function
main "$@"