Skip to main content

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

  1. Enable Debug Logging: Set MAIL_DEBUG=true environment variable
  2. Test Configuration: Use hypermodern mail test before sending
  3. Check Credentials: Verify SMTP credentials and permissions
  4. Network Issues: Test connectivity to SMTP server
  5. 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

VariableDescriptionDefault
MAIL_MAILERMail driver (smtp, gmail, etc.)smtp
MAIL_HOSTSMTP host-
MAIL_PORTSMTP port465
MAIL_USERNAMESMTP username-
MAIL_PASSWORDSMTP password-
MAIL_ENCRYPTIONEnable SSL/TLStrue
MAIL_FROM_ADDRESSDefault sender email-
MAIL_FROM_NAMEDefault sender name-
MAIL_ACCESS_TOKENOAuth2 access token-
MAIL_IGNORE_BAD_CERTIFICATEIgnore SSL certificate errorstrue

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.