Initial commit
This commit is contained in:
363
skills/mailhog/SKILL.md
Normal file
363
skills/mailhog/SKILL.md
Normal 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
|
||||
97
skills/mailhog/examples/app-configs/nodemailer.js
Normal file
97
skills/mailhog/examples/app-configs/nodemailer.js
Normal 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
|
||||
};
|
||||
255
skills/mailhog/examples/app-configs/python-smtp.py
Normal file
255
skills/mailhog/examples/app-configs/python-smtp.py
Normal 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"
|
||||
)
|
||||
52
skills/mailhog/examples/docker-compose.yml
Normal file
52
skills/mailhog/examples/docker-compose.yml
Normal 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:
|
||||
620
skills/mailhog/references/api-endpoints.md
Normal file
620
skills/mailhog/references/api-endpoints.md
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
378
skills/mailhog/references/configurations.md
Normal file
378
skills/mailhog/references/configurations.md
Normal 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"
|
||||
```
|
||||
1108
skills/mailhog/references/integration-patterns.md
Normal file
1108
skills/mailhog/references/integration-patterns.md
Normal file
File diff suppressed because it is too large
Load Diff
861
skills/mailhog/scripts/mailhog_manager.sh
Executable file
861
skills/mailhog/scripts/mailhog_manager.sh
Executable 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 "$@"
|
||||
493
skills/mailhog/scripts/send_test_email.sh
Executable file
493
skills/mailhog/scripts/send_test_email.sh
Executable 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 "$@"
|
||||
855
skills/mailhog/scripts/test_email_workflow.sh
Executable file
855
skills/mailhog/scripts/test_email_workflow.sh
Executable 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 "$@"
|
||||
Reference in New Issue
Block a user