766 lines
18 KiB
Markdown
Raw Normal View History

# Email System
<div align="center">
<img src="../../../logos/rustelo_dev-logo-h.svg" alt="RUSTELO" width="300" />
</div>
This guide covers RUSTELO's comprehensive email system, including setup, configuration, usage, and best practices for integrating email functionality into your application.
## Overview
The RUSTELO email system provides a complete solution for sending emails from your web application with support for multiple providers, template-based emails, form submissions, and both server-side and client-side integration.
### Architecture
- **Email Service**: Core service that handles email sending
- **Providers**: Pluggable email providers (SMTP, SendGrid, Console)
- **Templates**: Handlebars-based email templates
- **Forms**: Ready-to-use contact and support form components
- **API**: REST endpoints for email operations
## Features
**Multiple Providers**
- SMTP (Gmail, Outlook, custom servers)
- SendGrid API
- Console output (development)
📧 **Template System**
- Handlebars templates
- HTML and text versions
- Custom helpers
- Variable substitution
🔧 **Form Integration**
- Contact forms
- Support forms with priorities
- Custom form handling
🛡️ **Security**
- Input validation
- Rate limiting
- CSRF protection
- Secure configuration
🎨 **Rich Components**
- React/Leptos form components
- Real-time validation
- Error handling
- Success feedback
## Quick Start
### 1. Enable Email Feature
Make sure the email feature is enabled in your `Cargo.toml`:
```toml
[features]
default = ["email"]
email = ["lettre", "handlebars", "urlencoding"]
```
### 2. Basic Configuration
Add email configuration to your `config.toml`:
```toml
[email]
enabled = true
provider = "console" # Start with console for development
from_email = "noreply@yourapp.com"
from_name = "Your App"
template_dir = "templates/email"
```
### 3. Create Template Directory
```bash
mkdir -p templates/email/html
mkdir -p templates/email/text
```
### 4. Start Using
```rust
// Send a simple email
let result = email_service.send_simple_email(
"user@example.com",
"Welcome!",
"Thank you for signing up!"
).await?;
// Send a contact form
let result = email_service.send_contact_form(
"John Doe",
"john@example.com",
"Question about pricing",
"I'd like to know more about your pricing plans.",
"admin@yourapp.com"
).await?;
```
## Configuration
### Email Configuration Options
```toml
[email]
# Basic settings
enabled = true
provider = "smtp" # "smtp", "sendgrid", "console"
from_email = "noreply@yourapp.com"
from_name = "Your App Name"
template_dir = "templates/email"
# SMTP settings (when provider = "smtp")
smtp_host = "smtp.gmail.com"
smtp_port = 587
smtp_username = "your-email@gmail.com"
smtp_password = "@encrypted_smtp_password"
smtp_use_tls = false
smtp_use_starttls = true
# SendGrid settings (when provider = "sendgrid")
sendgrid_api_key = "@encrypted_sendgrid_api_key"
sendgrid_endpoint = "https://api.sendgrid.com/v3/mail/send"
```
### Environment-Specific Configuration
```toml
# Development
[environments.development]
email.provider = "console"
email.enabled = true
# Production
[environments.production]
email.provider = "sendgrid"
email.sendgrid_api_key = "@encrypted_sendgrid_api_key"
email.enabled = true
```
## Email Providers
### Console Provider
Perfect for development and testing. Prints emails to the console.
```toml
[email]
provider = "console"
```
**Features:**
- No external dependencies
- Immediate feedback
- Safe for development
### SMTP Provider
Use any SMTP server including Gmail, Outlook, or custom servers.
```toml
[email]
provider = "smtp"
smtp_host = "smtp.gmail.com"
smtp_port = 587
smtp_username = "your-email@gmail.com"
smtp_password = "@encrypted_smtp_password"
smtp_use_starttls = true
```
**Common SMTP Configurations:**
**Gmail:**
```toml
smtp_host = "smtp.gmail.com"
smtp_port = 587
smtp_use_starttls = true
# Requires App Password (not regular password)
```
**Outlook:**
```toml
smtp_host = "smtp-mail.outlook.com"
smtp_port = 587
smtp_use_starttls = true
```
**Custom SMTP:**
```toml
smtp_host = "mail.yourserver.com"
smtp_port = 587
smtp_use_starttls = true
```
### SendGrid Provider
Use SendGrid's API for reliable email delivery.
```toml
[email]
provider = "sendgrid"
sendgrid_api_key = "@encrypted_sendgrid_api_key"
```
**Features:**
- High deliverability
- Built-in analytics
- Bounce handling
- Reliable service
## Templates
### Template Structure
```
templates/email/
├── html/
│ ├── contact.hbs
│ ├── support.hbs
│ ├── welcome.hbs
│ └── notification.hbs
└── text/
├── contact.hbs
├── support.hbs
├── welcome.hbs
└── notification.hbs
```
### Template Naming
- `contact.hbs` - Contact form emails
- `support.hbs` - Support form emails
- `welcome.hbs` - Welcome/registration emails
- `notification.hbs` - General notifications
### Handlebars Helpers
Built-in helpers for common email tasks:
- `{{format_date}}` - Format dates
- `{{format_currency}}` - Format money
- `{{upper}}` - Uppercase text
- `{{lower}}` - Lowercase text
- `{{capitalize}}` - Capitalize words
### Example Template
**templates/email/html/contact.hbs:**
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Contact Form Submission</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background-color: #f8f9fa; padding: 20px; border-radius: 8px; }
.content { margin: 20px 0; }
.footer { color: #666; font-size: 12px; }
</style>
</head>
<body>
<div class="header">
<h1>New Contact Form Submission</h1>
</div>
<div class="content">
<p><strong>Name:</strong> {{name}}</p>
<p><strong>Email:</strong> {{email}}</p>
<p><strong>Subject:</strong> {{subject}}</p>
<p><strong>Message:</strong></p>
<p>{{message}}</p>
</div>
<div class="footer">
<p>Sent at {{format_date timestamp}}</p>
</div>
</body>
</html>
```
**templates/email/text/contact.hbs:**
```text
New Contact Form Submission
Name: {{name}}
Email: {{email}}
Subject: {{subject}}
Message:
{{message}}
Sent at {{format_date timestamp}}
```
## API Endpoints
### GET /api/email/status
Check email system status.
**Response:**
```json
{
"enabled": true,
"provider": "smtp",
"configured": true,
"templates": ["contact", "support", "welcome", "notification"]
}
```
### POST /api/email/contact
Send a contact form email.
**Request:**
```json
{
"name": "John Doe",
"email": "john@example.com",
"subject": "Question about pricing",
"message": "I'd like to know more about your pricing plans.",
"recipient": "admin@yourapp.com"
}
```
**Response:**
```json
{
"message": "Email sent successfully",
"message_id": "abc123def456",
"status": "sent"
}
```
### POST /api/email/support
Send a support form email with priority.
**Request:**
```json
{
"name": "Jane Smith",
"email": "jane@example.com",
"subject": "Technical Issue",
"message": "Having trouble with login functionality.",
"priority": "high",
"category": "technical",
"recipient": "support@yourapp.com"
}
```
### POST /api/email/send
Send a template-based email.
**Request:**
```json
{
"to": "user@example.com",
"subject": "Welcome to Our Platform",
"template": "welcome",
"template_data": {
"user_name": "John Doe",
"activation_link": "https://yourapp.com/activate/token123"
}
}
```
### POST /api/email/notification
Send a notification email.
**Request:**
```json
{
"to": "user@example.com",
"title": "Important Update",
"message": "Your account has been updated successfully.",
"content": "Additional details about the update..."
}
```
## Client Components
### ContactForm Component
```rust
#[component]
pub fn ContactForm() -> impl IntoView {
let (form_data, set_form_data) = create_signal(ContactFormData::default());
let (is_submitting, set_is_submitting) = create_signal(false);
let (message, set_message) = create_signal(String::new());
let submit_form = create_action(move |data: &ContactFormData| {
let data = data.clone();
async move {
set_is_submitting(true);
let result = send_contact_form(data).await;
set_is_submitting(false);
match result {
Ok(_) => set_message("Thank you for your message! We'll get back to you soon.".to_string()),
Err(e) => set_message(format!("Error sending message: {}", e)),
}
}
});
view! {
<div class="contact-form">
<h2>"Contact Us"</h2>
<form on:submit=move |ev| {
ev.prevent_default();
submit_form.dispatch(form_data.get());
}>
<div class="form-group">
<label for="name">"Name"</label>
<input
type="text"
id="name"
required
on:input=move |ev| {
set_form_data.update(|data| data.name = event_target_value(&ev));
}
/>
</div>
<div class="form-group">
<label for="email">"Email"</label>
<input
type="email"
id="email"
required
on:input=move |ev| {
set_form_data.update(|data| data.email = event_target_value(&ev));
}
/>
</div>
<div class="form-group">
<label for="subject">"Subject"</label>
<input
type="text"
id="subject"
required
on:input=move |ev| {
set_form_data.update(|data| data.subject = event_target_value(&ev));
}
/>
</div>
<div class="form-group">
<label for="message">"Message"</label>
<textarea
id="message"
required
on:input=move |ev| {
set_form_data.update(|data| data.message = event_target_value(&ev));
}
></textarea>
</div>
<button type="submit" disabled=move || is_submitting.get()>
{move || if is_submitting.get() { "Sending..." } else { "Send Message" }}
</button>
</form>
<Show when=move || !message.get().is_empty()>
<div class="message">{move || message.get()}</div>
</Show>
</div>
}
}
```
### SupportForm Component
```rust
#[component]
pub fn SupportForm() -> impl IntoView {
let (form_data, set_form_data) = create_signal(SupportFormData::default());
let (is_submitting, set_is_submitting) = create_signal(false);
let (message, set_message) = create_signal(String::new());
let submit_form = create_action(move |data: &SupportFormData| {
let data = data.clone();
async move {
set_is_submitting(true);
let result = send_support_form(data).await;
set_is_submitting(false);
match result {
Ok(_) => set_message("Support ticket submitted successfully!".to_string()),
Err(e) => set_message(format!("Error submitting ticket: {}", e)),
}
}
});
view! {
<div class="support-form">
<h2>"Support Request"</h2>
<form on:submit=move |ev| {
ev.prevent_default();
submit_form.dispatch(form_data.get());
}>
// Similar form fields with priority selector
<div class="form-group">
<label for="priority">"Priority"</label>
<select
id="priority"
on:change=move |ev| {
set_form_data.update(|data| data.priority = event_target_value(&ev));
}
>
<option value="low">"Low"</option>
<option value="medium">"Medium"</option>
<option value="high">"High"</option>
<option value="urgent">"Urgent"</option>
</select>
</div>
<button type="submit" disabled=move || is_submitting.get()>
{move || if is_submitting.get() { "Submitting..." } else { "Submit Ticket" }}
</button>
</form>
</div>
}
}
```
## Server Usage
### Basic Email Sending
```rust
use server::email::{EmailService, EmailConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = EmailConfig::from_file("config.toml")?;
let email_service = EmailService::new(config).await?;
// Send simple email
email_service.send_simple_email(
"user@example.com",
"Welcome!",
"Thank you for signing up!"
).await?;
Ok(())
}
```
### Template-Based Emails
```rust
use std::collections::HashMap;
#[server(SendWelcomeEmail, "/api/email/welcome")]
pub async fn send_welcome_email(
email: String,
name: String,
activation_token: String,
) -> Result<String, ServerFnError> {
let email_service = get_email_service().await?;
let mut template_data = HashMap::new();
template_data.insert("user_name".to_string(), name);
template_data.insert("activation_link".to_string(),
format!("https://yourapp.com/activate/{}", activation_token));
email_service.send_template_email(
&email,
"Welcome to Our Platform",
"welcome",
template_data
).await?;
Ok("Welcome email sent successfully".to_string())
}
```
### Form Handling
```rust
#[server(HandleContactForm, "/api/email/contact")]
pub async fn handle_contact_form(
name: String,
email: String,
subject: String,
message: String,
) -> Result<String, ServerFnError> {
let email_service = get_email_service().await?;
// Validate input
if name.is_empty() || email.is_empty() || message.is_empty() {
return Err(ServerFnError::ServerError("All fields are required".to_string()));
}
// Send email
email_service.send_contact_form(
&name,
&email,
&subject,
&message,
"admin@yourapp.com"
).await?;
Ok("Contact form submitted successfully".to_string())
}
```
## Environment Variables
### Common Variables
```bash
# SMTP Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_USE_STARTTLS=true
# SendGrid Configuration
SENDGRID_API_KEY=your-sendgrid-api-key
# Email Settings
EMAIL_FROM=noreply@yourapp.com
EMAIL_FROM_NAME="Your App"
```
### Using in Configuration
```toml
[email]
smtp_username = "${SMTP_USERNAME}"
smtp_password = "@encrypted_smtp_password"
sendgrid_api_key = "@encrypted_sendgrid_api_key"
from_email = "${EMAIL_FROM}"
```
## Security Considerations
### 1. Credential Management
Always encrypt sensitive credentials:
```bash
# Encrypt SMTP password
cargo run --bin config_crypto_tool encrypt "your-smtp-password"
# Encrypt SendGrid API key
cargo run --bin config_crypto_tool encrypt "your-sendgrid-api-key"
```
### 2. Input Validation
Always validate email inputs:
```rust
use validator::{Validate, ValidationError};
#[derive(Validate)]
struct ContactFormData {
#[validate(length(min = 1, message = "Name is required"))]
name: String,
#[validate(email(message = "Invalid email address"))]
email: String,
#[validate(length(min = 1, max = 1000, message = "Message must be between 1 and 1000 characters"))]
message: String,
}
```
### 3. Rate Limiting
Implement rate limiting for email endpoints:
```rust
use tower_governor::{governor::GovernorLayer, GovernorConfigBuilder};
// Limit to 5 emails per minute per IP
let governor_config = GovernorConfigBuilder::default()
.per_minute(5)
.burst_size(2)
.finish()
.unwrap();
let governor_layer = GovernorLayer {
config: Arc::new(governor_config),
};
```
### 4. CSRF Protection
Enable CSRF protection for email forms:
```rust
use axum_csrf::{CsrfConfig, CsrfLayer};
let csrf_config = CsrfConfig::default();
let csrf_layer = CsrfLayer::new(csrf_config);
```
## Troubleshooting
### Common Issues
**Email not sending:**
- Check provider configuration
- Verify credentials
- Check network connectivity
- Review email service logs
**Template not found:**
- Verify template directory path
- Check template file naming
- Ensure HTML and text versions exist
**Authentication failed:**
- For Gmail: Use App Password, not regular password
- For other providers: Check username/password
- Verify server and port settings
**Rate limiting:**
- Check provider limits
- Implement proper rate limiting
- Consider using queues for bulk emails
### Debug Mode
Enable debug logging to troubleshoot issues:
```toml
[logging]
level = "debug"
[email]
enabled = true
debug = true
```
## Best Practices
1. **Use encrypted configuration** for sensitive credentials
2. **Implement proper validation** for all email inputs
3. **Use rate limiting** to prevent abuse
4. **Provide both HTML and text** versions of templates
5. **Test with console provider** during development
6. **Monitor email delivery** in production
7. **Handle errors gracefully** with user-friendly messages
8. **Use meaningful subject lines** and sender names
## Next Steps
- [API Reference](../../api/email.md)
- [Security Best Practices](../../security/best-practices.md)
- [Configuration Guide](../../configuration/files.md)
- [Deployment Guide](../../deployment/production.md)
The email system provides a robust foundation for all your application's communication needs while maintaining security and reliability.