Mail System
The Hypermodern Mail System provides a comprehensive email solution that integrates seamlessly with your applications. It offers both programmatic APIs for sending emails from your code and CLI commands for administrative tasks and testing.
Overview
The mail system is built around several key concepts:
- Mail Classes: Object-oriented approach to email composition
- Multiple Providers: Support for various SMTP providers and services
- Template System: Dynamic content with variable substitution
- CLI Integration: Command-line tools for testing and sending emails
- Environment Configuration: Secure credential management
Installation and Setup
The mail functionality is included in both the hypermodern library and hypermodern_cli packages. No additional installation is required beyond the standard Hypermodern setup.
Dependencies
The mail system uses the mailer package internally:
dependencies:
hypermodern: ^1.0.0
mailer: ^6.4.1 # Automatically included
Configuration
Configure your mail settings using environment variables for security and flexibility:
# Basic SMTP Configuration
export MAIL_MAILER=smtp
export MAIL_HOST=smtp.example.com
export MAIL_PORT=587
export MAIL_USERNAME=your-email@example.com
export MAIL_PASSWORD=your-password
export MAIL_ENCRYPTION=true
export MAIL_FROM_ADDRESS=noreply@example.com
export MAIL_FROM_NAME="Your App Name"
Provider-Specific Configurations
Gmail
export MAIL_MAILER=gmail
export MAIL_USERNAME=your-gmail@gmail.com
export MAIL_PASSWORD=your-app-password
Mailgun
export MAIL_MAILER=mailgun
export MAIL_USERNAME=postmaster@mg.yourdomain.com
export MAIL_PASSWORD=your-mailgun-key
Generic SMTP
export MAIL_MAILER=smtp
export MAIL_HOST=smtp.yourdomain.com
export MAIL_PORT=587
export MAIL_USERNAME=your-email@yourdomain.com
export MAIL_PASSWORD=your-password
export MAIL_ENCRYPTION=true
Supported Providers
- smtp: Generic SMTP server
- gmail: Gmail SMTP
- gmailSaslXoauth2: Gmail with OAuth2
- hotmail: Hotmail/Outlook SMTP
- mailgun: Mailgun SMTP
- qq: QQ Mail SMTP
- yahoo: Yahoo Mail SMTP
- yandex: Yandex Mail SMTP
Creating Mail Classes
Basic Mail Class
Generate a new mail class using the CLI:
hypermodern generate:mail Welcome
This creates a mail class template:
import 'package:hypermodern/mail.dart';
/// Welcome mail class
class WelcomeMail extends Mail {
final String userName;
final String userEmail;
const WelcomeMail({
required this.userName,
required this.userEmail,
});
@override
Envelope envelope() {
return Envelope(
to: [Address(userEmail, userName)],
subject: 'Welcome to Our App!',
);
}
@override
Content content() {
return Content(
text: '''
Hello $userName,
Welcome to our application! We're excited to have you on board.
Best regards,
The Team
''',
html: '''
<h1>Hello $userName</h1>
<p>Welcome to our application! We're excited to have you on board.</p>
<p>Best regards,<br>The Team</p>
''',
);
}
@override
List<Attachment>? attachments() => null;
@override
MailView? view() => null;
}
Advanced Mail Features
Multiple Recipients
@override
Envelope envelope() {
return Envelope(
to: [
Address('user1@example.com', 'User One'),
Address('user2@example.com', 'User Two'),
],
cc: [Address('manager@example.com', 'Manager')],
bcc: [Address('admin@example.com', 'Admin')],
subject: 'Team Notification',
);
}
File Attachments
@override
List<Attachment> attachments() {
return [
FileAttachment(File('documents/welcome_guide.pdf')),
FileAttachment(File('images/logo.png')),
];
}
Template-Based Emails
@override
MailView view() {
return MailView(
view: '''
<div style="font-family: Arial, sans-serif;">
<h1>Hello {{userName}}</h1>
<p>Your account has been created successfully.</p>
<p><a href="{{activationLink}}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Activate Account</a></p>
<p>Best regards,<br>{{companyName}}</p>
</div>
''',
data: {
'userName': userName,
'activationLink': activationLink,
'companyName': 'Your Company',
},
);
}
Sending Emails
Programmatic Sending
// Create and send a welcome email
final welcomeMail = WelcomeMail(
userName: 'Alice Smith',
userEmail: 'alice@example.com',
);
try {
final report = await welcomeMail.send();
print('Email sent successfully!');
} catch (e) {
print('Failed to send email: $e');
}
Custom SMTP Settings
You can override environment settings when sending:
final report = await welcomeMail.send(
host: 'custom-smtp.example.com',
username: 'custom-user@example.com',
password: 'custom-password',
port: 587,
ssl: true,
mailDriver: 'smtp',
);
CLI Commands
Sending Emails from Command Line
# Send a simple text email
hypermodern mail send \
--to "recipient@example.com" \
--subject "Hello World" \
--body "This is a test email"
# Send HTML email
hypermodern mail send \
--to "recipient@example.com" \
--subject "HTML Email" \
--body "<h1>Hello</h1><p>This is HTML content</p>" \
--html
Testing SMTP Configuration
# Test with environment variables
hypermodern mail test
# Test with custom settings
hypermodern mail test \
--host "smtp.gmail.com" \
--username "your-email@gmail.com" \
--password "your-password" \
--port 587 \
--ssl
Common Use Cases
User Registration Email
class UserRegistrationMail extends Mail {
final String userName;
final String userEmail;
final String activationToken;
const UserRegistrationMail({
required this.userName,
required this.userEmail,
required this.activationToken,
});
@override
Envelope envelope() {
return Envelope(
to: [Address(userEmail, userName)],
subject: 'Activate Your Account',
);
}
@override
MailView view() {
return MailView(
view: '''
<h1>Welcome {{userName}}!</h1>
<p>Thank you for registering. Please click the link below to activate your account:</p>
<p><a href="{{activationLink}}">Activate Account</a></p>
<p>This link will expire in 24 hours.</p>
''',
data: {
'userName': userName,
'activationLink': 'https://yourapp.com/activate?token=$activationToken',
},
);
}
}
Password Reset Email
class PasswordResetMail extends Mail {
final String userName;
final String userEmail;
final String resetToken;
const PasswordResetMail({
required this.userName,
required this.userEmail,
required this.resetToken,
});
@override
Envelope envelope() {
return Envelope(
to: [Address(userEmail, userName)],
subject: 'Password Reset Request',
);
}
@override
MailView view() {
return MailView(
view: '''
<h1>Password Reset</h1>
<p>Hello {{userName}},</p>
<p>You requested a password reset. Click the link below to reset your password:</p>
<p><a href="{{resetLink}}" style="background-color: #dc3545; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Reset Password</a></p>
<p>If you didn't request this, please ignore this email.</p>
<p>This link will expire in 1 hour.</p>
''',
data: {
'userName': userName,
'resetLink': 'https://yourapp.com/reset?token=$resetToken',
},
);
}
}
Order Confirmation Email
class OrderConfirmationMail extends Mail {
final String customerName;
final String customerEmail;
final String orderNumber;
final List<OrderItem> items;
final double totalAmount;
final File? invoicePdf;
const OrderConfirmationMail({
required this.customerName,
required this.customerEmail,
required this.orderNumber,
required this.items,
required this.totalAmount,
this.invoicePdf,
});
@override
Envelope envelope() {
return Envelope(
to: [Address(customerEmail, customerName)],
subject: 'Order Confirmation #$orderNumber',
);
}
@override
MailView view() {
final itemsHtml = items.map((item) =>
'<tr><td>${item.name}</td><td>${item.quantity}</td><td>\$${item.price}</td></tr>'
).join('\n');
return MailView(
view: '''
<h1>Order Confirmation</h1>
<p>Hello {{customerName}},</p>
<p>Thank you for your order! Here are the details:</p>
<h2>Order #{{orderNumber}}</h2>
<table border="1" style="border-collapse: collapse; width: 100%;">
<tr><th>Item</th><th>Quantity</th><th>Price</th></tr>
{{itemsHtml}}
<tr><td colspan="2"><strong>Total</strong></td><td><strong>\${{totalAmount}}</strong></td></tr>
</table>
<p>Your order will be processed within 1-2 business days.</p>
''',
data: {
'customerName': customerName,
'orderNumber': orderNumber,
'itemsHtml': itemsHtml,
'totalAmount': totalAmount.toStringAsFixed(2),
},
);
}
@override
List<Attachment>? attachments() {
return invoicePdf != null ? [FileAttachment(invoicePdf!)] : null;
}
}
Integration with Background Jobs
Combine the mail system with background jobs for reliable email delivery:
// Generate a mail job
hypermodern generate:job SendWelcomeEmail --template email
// Implementation
class SendWelcomeEmailJob extends Job {
@override
String get identifier => 'send_welcome_email';
@override
Future<void> execute(Map<String, dynamic> parameters) async {
final userName = parameters['userName'] as String;
final userEmail = parameters['userEmail'] as String;
final mail = WelcomeMail(
userName: userName,
userEmail: userEmail,
);
try {
await mail.send();
logger.info('Welcome email sent to $userEmail');
} catch (e) {
logger.error('Failed to send welcome email to $userEmail: $e');
rethrow; // This will trigger job retry
}
}
}
// Schedule the job
await jobScheduler.schedule(
'send_welcome_email',
parameters: {
'userName': 'John Doe',
'userEmail': 'john@example.com',
},
delay: Duration(minutes: 5),
);
Error Handling and Debugging
Common Issues and Solutions
Authentication Failed
try {
await mail.send();
} catch (e) {
if (e.toString().contains('authentication')) {
logger.error('SMTP authentication failed. Check username/password.');
// Handle authentication error
}
}
Connection Timeout
try {
await mail.send();
} catch (e) {
if (e.toString().contains('timeout')) {
logger.error('SMTP connection timeout. Check host/port settings.');
// Handle timeout error
}
}
Rate Limiting
class RateLimitedMailer {
static const Duration _delay = Duration(seconds: 1);
static DateTime _lastSent = DateTime(0);
static Future<void> sendWithRateLimit(Mail mail) async {
final now = DateTime.now();
final timeSinceLastSend = now.difference(_lastSent);
if (timeSinceLastSend < _delay) {
await Future.delayed(_delay - timeSinceLastSend);
}
await mail.send();
_lastSent = DateTime.now();
}
}
Debugging Tips
- Enable Debug Logging: Set
MAIL_DEBUG=trueenvironment variable - Test Configuration: Use
hypermodern mail testbefore sending - Check Credentials: Verify SMTP credentials and permissions
- Network Issues: Test connectivity to SMTP server
- Provider Limits: Be aware of sending limits and rate restrictions
Best Practices
Security
- Store SMTP credentials in environment variables
- Use app-specific passwords for Gmail and other providers
- Enable SSL/TLS encryption for all connections
- Rotate credentials regularly
Performance
- Use background jobs for bulk email sending
- Implement rate limiting to avoid provider restrictions
- Cache SMTP connections when possible
- Monitor email delivery success rates
Content
- Always provide both text and HTML versions
- Use responsive HTML templates for mobile compatibility
- Include unsubscribe links for marketing emails
- Test emails across different clients and devices
Reliability
- Implement retry logic for failed sends
- Log all email activities for debugging
- Use dead letter queues for permanently failed emails
- Monitor bounce rates and delivery statistics
Environment Variables Reference
| Variable | Description | Default |
|---|---|---|
MAIL_MAILER |
Mail driver (smtp, gmail, etc.) | smtp |
MAIL_HOST |
SMTP host | - |
MAIL_PORT |
SMTP port | 465 |
MAIL_USERNAME |
SMTP username | - |
MAIL_PASSWORD |
SMTP password | - |
MAIL_ENCRYPTION |
Enable SSL/TLS | true |
MAIL_FROM_ADDRESS |
Default sender email | - |
MAIL_FROM_NAME |
Default sender name | - |
MAIL_ACCESS_TOKEN |
OAuth2 access token | - |
MAIL_IGNORE_BAD_CERTIFICATE |
Ignore SSL certificate errors | true |
Conclusion
The Hypermodern Mail System provides a robust, flexible solution for email functionality in your applications. By combining object-oriented mail classes with powerful CLI tools and comprehensive provider support, it enables both simple and complex email scenarios while maintaining security and reliability.
The integration with background jobs ensures reliable delivery, while the template system allows for dynamic, professional-looking emails. Whether you're sending simple notifications or complex transactional emails with attachments, the mail system provides the tools you need to communicate effectively with your users.
No Comments