Skip to content

Email Providers

FORGE supports sending transactional emails through multiple provider drivers. The email system handles account verification, password resets, notifications, and any custom email your application needs to send.

EmailProvider Trait

All email drivers implement the EmailProvider trait:

rust
#[async_trait]
pub trait EmailProvider: Send + Sync {
    /// Send a structured email message
    async fn send(&self, message: EmailMessage) -> Result<EmailResult, EmailError>;

    /// Send a templated email with dynamic data
    async fn send_template(
        &self,
        to: &str,
        template: &str,
        data: &serde_json::Value,
    ) -> Result<EmailResult, EmailError>;

    /// Validate an email address format and optionally check deliverability
    async fn validate_email(&self, email: &str) -> Result<bool, EmailError>;
}

EmailMessage Struct

The EmailMessage struct provides full control over email composition:

rust
pub struct EmailMessage {
    /// Recipient email address(es)
    pub to: Vec<String>,
    /// Carbon copy recipients
    pub cc: Vec<String>,
    /// Blind carbon copy recipients
    pub bcc: Vec<String>,
    /// Email subject line
    pub subject: String,
    /// Plain text body (fallback for HTML-incapable clients)
    pub body_text: Option<String>,
    /// HTML body content
    pub body_html: Option<String>,
    /// File attachments
    pub attachments: Vec<EmailAttachment>,
    /// Reply-to address (overrides the default from address)
    pub reply_to: Option<String>,
}

pub struct EmailAttachment {
    /// File name as it appears in the email
    pub filename: String,
    /// MIME type (e.g., "application/pdf")
    pub content_type: String,
    /// Raw file bytes
    pub data: Vec<u8>,
}

EmailResult Struct

Every send operation returns an EmailResult confirming delivery:

rust
pub struct EmailResult {
    /// Unique message identifier from the provider
    pub message_id: String,
    /// Delivery status
    pub status: EmailStatus,
}

pub enum EmailStatus {
    /// Message accepted by the provider for delivery
    Queued,
    /// Message delivered to the recipient's mail server
    Delivered,
    /// Delivery failed
    Failed(String),
}

Available Drivers

SMTP (Default)

Standard SMTP email delivery. This is the default driver and requires no additional installation. It works with any standard SMTP server including Gmail, Outlook, Amazon SES relay, and local development servers like Mailhog.

Configuration:

SettingTypeDefaultDescription
email_hoststring--SMTP server hostname
email_portnumber587SMTP server port
email_usernamestring--SMTP authentication username
email_passwordencrypted--SMTP authentication password
email_encryptionstring"tls"Encryption method: tls, ssl, or none
email_from_addressstring--Default sender email address
email_from_namestring--Default sender display name

TIP

SMTP is the default driver and works out of the box. For local development, use Mailhog with email_host: localhost, email_port: 1025, and email_encryption: none to capture all outgoing emails without actually delivering them.

Common SMTP ports
PortProtocolUse Case
25SMTPServer-to-server relay (often blocked by ISPs)
465SMTPSImplicit SSL/TLS
587SMTPSubmission with STARTTLS (recommended)
1025SMTPMailhog / local development

SendGrid

Cloud email delivery via the SendGrid API. SendGrid provides high-volume sending, delivery analytics, and template management through their dashboard.

Installation:

bash
forge provider:add email:sendgrid

Configuration:

SettingTypeDescription
sendgrid_api_keyencryptedSendGrid API Key
email_from_addressstringVerified sender email address
email_from_namestringDefault sender display name

Features:

  • Transactional emails -- High-deliverability sending optimized for application-generated messages
  • Template management -- Create and manage email templates in the SendGrid dashboard, then reference them by ID
  • Delivery tracking -- Real-time delivery status, open rates, and click tracking
  • Webhooks -- Receive callbacks for delivery events (delivered, bounced, opened, clicked)

WARNING

SendGrid requires sender verification. Ensure your email_from_address is verified in your SendGrid account before sending. Unverified senders will be rejected.

Mailgun

Email delivery via the Mailgun API.

Installation:

bash
forge provider:add email:mailgun

Configuration:

SettingTypeDescription
mailgun_api_keyencryptedMailgun API Key
mailgun_domainstringMailgun sending domain
mailgun_regionstringAPI region: us or eu
email_from_addressstringDefault sender address
email_from_namestringDefault sender display name

Amazon SES

Email delivery via Amazon Simple Email Service.

Installation:

bash
forge provider:add email:ses

Configuration:

SettingTypeDescription
ses_access_keyencryptedAWS Access Key ID
ses_secret_keyencryptedAWS Secret Access Key
ses_regionstringAWS region (e.g., us-east-1)
email_from_addressstringVerified sender address
email_from_namestringDefault sender display name

WARNING

Amazon SES starts in sandbox mode, which only allows sending to verified email addresses. Request production access through the AWS console for unrestricted sending.

Usage

Creating the Provider

The factory creates the correct driver based on your configuration:

rust
use crate::services::email::EmailFactory;

let email = EmailFactory::create(&settings).await?;

Sending a Plain Email

rust
let message = EmailMessage {
    to: vec!["user@example.com".to_string()],
    cc: vec![],
    bcc: vec![],
    subject: "Welcome to Our App".to_string(),
    body_text: Some("Thanks for signing up!".to_string()),
    body_html: Some("<h1>Welcome!</h1><p>Thanks for signing up.</p>".to_string()),
    attachments: vec![],
    reply_to: None,
};

let result = email.send(message).await?;
println!("Sent with message ID: {}", result.message_id);

Sending with Attachments

rust
let pdf_bytes = std::fs::read("invoice.pdf")?;

let message = EmailMessage {
    to: vec!["billing@example.com".to_string()],
    cc: vec!["accounting@example.com".to_string()],
    bcc: vec![],
    subject: "Invoice #1234".to_string(),
    body_text: Some("Please find your invoice attached.".to_string()),
    body_html: Some("<p>Please find your invoice attached.</p>".to_string()),
    attachments: vec![
        EmailAttachment {
            filename: "invoice-1234.pdf".to_string(),
            content_type: "application/pdf".to_string(),
            data: pdf_bytes,
        }
    ],
    reply_to: Some("billing@mycompany.com".to_string()),
};

let result = email.send(message).await?;

Sending a Templated Email

rust
use serde_json::json;

let result = email.send_template(
    "user@example.com",
    "welcome",
    &json!({
        "name": "Jane Doe",
        "app_name": "My Application",
        "verification_url": "https://example.com/verify?token=abc123"
    })
).await?;

TIP

Template-based emails are recommended for all user-facing messages. They keep email content separate from application logic and allow non-developers to modify email copy through the admin panel or provider dashboard.

Validating an Email Address

rust
let is_valid = email.validate_email("user@example.com").await?;

if !is_valid {
    return Err(AppError::validation("Invalid email address"));
}

Configuration via Admin

Email settings are managed through the Settings admin page under the Email group:

Settings > Email
┌──────────────────────────────────────────────────┐
│  Driver:           [SMTP             v]          │
│  Host:             [smtp.example.com   ]         │
│  Port:             [587                ]         │
│  Username:         [noreply@example.com]         │
│  Password:         [................   ]         │
│  Encryption:       [TLS              v]          │
│  From Name:        [My Application     ]         │
│  From Address:     [noreply@example.com]         │
└──────────────────────────────────────────────────┘

Error Handling

All email operations return Result<EmailResult, EmailError> with structured error types:

rust
match email.send(message).await {
    Ok(result) => println!("Sent: {}", result.message_id),
    Err(EmailError::InvalidAddress) => println!("Bad email address"),
    Err(EmailError::ProviderError(msg)) => println!("Provider error: {}", msg),
    Err(EmailError::TemplateNotFound(name)) => println!("Template '{}' not found", name),
    Err(EmailError::AttachmentTooLarge(size)) => println!("Attachment exceeds limit: {} bytes", size),
    Err(e) => println!("Unexpected error: {}", e),
}

WARNING

Most email providers impose attachment size limits (typically 10-25 MB). For large files, consider uploading to Storage and including a download link in the email body instead.

Released under the MIT License.