# E Mail

## Description

Email providers in FlexBase expose a unified interface (`IFlexEmailProvider`) for sending transactional email via SMTP, SendGrid, AWS SES, Azure Communication Services, and more.

Your application code should depend on `IFlexEmailProvider`.

## Important concepts

* **`IFlexEmailProvider` is the contract**: send via `SendAsync(...)`, `SendHtmlAsync(...)`, `SendToManyAsync(...)`, `SendBulkAsync(...)`, and `SendTemplatedAsync(...)`.
* **Provider bridge**: generated infrastructure commonly registers `IFlexEmailProviderBridge` (which also implements `IFlexEmailProvider`) so behavior can be overridden safely.
* **Templates are optional**: template rendering is supported via `IFlexEmailTemplateEngine` (and provider-specific template features vary).

## Configuration in DI

Only register the provider—Flex auto-wires generated PostBus handlers/plugins that consume the interface.

```csharp
public static class OtherApplicationServicesConfig
{
    public static IServiceCollection AddOtherApplicationServices(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // Pick ONE (or register multiple with different compositions).
        services.AddFlexSmtpEmailProvider(configuration);
        // services.AddFlexSendGridEmailProvider(configuration);
        // services.AddFlexAwsSesEmailProvider(configuration);
        // services.AddFlexAzureCommunicationEmailProvider(configuration);
        return services;
    }
}
```

## appsettings.json

Email provider configuration is read from `FlexBase:Providers:Email:<Provider>`.

```json
{
  "FlexBase": {
    "Providers": {
      "Email": {
        "Smtp": {
          "Host": "smtp.office365.com",
          "Port": 587,
          "UseSsl": true,
          "UseStartTls": true,
          "Username": "user@company.com",
          "Password": "<store-in-secrets>",
          "DefaultFromEmail": "noreply@company.com",
          "DefaultFromName": "Company"
        },
        "SendGrid": {
          "ApiKey": "<store-in-secrets>",
          "DefaultFromEmail": "noreply@company.com",
          "DefaultFromName": "Company",
          "EnableTracking": true,
          "EnableClickTracking": true,
          "EnableOpenTracking": true,
          "MaxRetries": 3,
          "Timeout": "00:00:30"
        },
        "AwsSes": {
          "AccessKeyId": "<store-in-secrets>",
          "SecretAccessKey": "<store-in-secrets>",
          "Region": "us-east-1",
          "DefaultFromEmail": "noreply@company.com",
          "DefaultFromName": "Company",
          "ConfigurationSetName": null,
          "MaxRetries": 3,
          "Timeout": "00:00:30"
        },
        "AzureCommunication": {
          "ConnectionString": "<store-in-secrets>",
          "DefaultFromEmail": "noreply@company.com",
          "DefaultFromName": "Company",
          "MaxRetries": 3,
          "Timeout": "00:00:30",
          "PollingInterval": "00:00:01"
        }
      }
    }
  }
}
```

## Examples (template-based)

This mirrors the generated PostBus handler template shape. You do **not** register the handler manually.

```csharp
using Microsoft.Extensions.Logging;
using Sumeru.Flex;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace {YourApplication}.PostBusHandlers.Email;

public partial class SendEmailHandler : ISendEmailHandler
{
    protected string EventCondition = string.Empty;

    protected readonly ILogger<SendEmailHandler> _logger;
    protected readonly IFlexHost _flexHost;
    protected readonly IFlexEmailProvider _emailProvider;

    protected FlexAppContextBridge? _flexAppContext;

    public SendEmailHandler(
        ILogger<SendEmailHandler> logger,
        IFlexHost flexHost,
        IFlexEmailProvider emailProvider)
    {
        _logger = logger;
        _flexHost = flexHost;
        _emailProvider = emailProvider;
    }

    public virtual async Task Execute(SendEmailCommand cmd, IFlexServiceBusContext serviceBusContext)
    {
        _flexAppContext = cmd.Dto.GetAppContext();  //do not remove this line

        var result = await _emailProvider.SendAsync(
            to: cmd.Dto.To,
            subject: cmd.Dto.Subject,
            body: cmd.Dto.Body,
            from: cmd.Dto.From);

        cmd.Dto.MessageId = result.MessageId;
        cmd.Dto.IsSuccess = result.IsSuccess;

        await this.Fire(EventCondition, serviceBusContext);
    }
}
```

## Provider considerations

* Keep secrets (SMTP password, SendGrid API key, AWS keys, ACS connection string) out of source control.
* Use `TestConnectionAsync(...)` during startup checks/health probes.

  public async Task SendWelcomeEmailAsync(string email, string name) { var result = await \_emailProvider.SendAsync( to: email, subject: "Welcome!", body: $"Hello {name}, welcome to our platform!" );

  ```
    if (!result.IsSuccess)
    {
        _logger.LogError("Failed to send email: {Error}", result.ErrorMessage);
    }
  ```

  } }

````

### HTML Email

```csharp
public async Task SendHtmlEmailAsync(string to, string name)
{
    var htmlBody = $"""
        <html>
        <body>
            <h1>Welcome, {name}!</h1>
            <p>Thank you for joining our platform.</p>
            <a href="https://app.example.com">Get Started</a>
        </body>
        </html>
        """;

    var textBody = $"Welcome, {name}! Thank you for joining our platform.";

    var result = await _emailProvider.SendHtmlAsync(
        to: to,
        subject: "Welcome!",
        htmlBody: htmlBody,
        textBody: textBody  // Fallback for email clients that don't support HTML
    );
}
````

### Full Message Object

```csharp
public async Task SendCompleteEmailAsync()
{
    var message = new FlexEmailMessage
    {
        From = "sender@company.com",
        FromName = "Company Name",
        To = new List<string> { "recipient@email.com" },
        Cc = new List<string> { "cc@email.com" },
        Bcc = new List<string> { "bcc@email.com" },
        ReplyTo = "reply@company.com",
        Subject = "Important Update",
        TextBody = "Plain text content",
        HtmlBody = "<h1>HTML content</h1>",
        Priority = FlexEmailPriority.High,
        Attachments = new List<FlexEmailAttachment>
        {
            new FlexEmailAttachment
            {
                FileName = "document.pdf",
                ContentType = "application/pdf",
                Content = pdfBytes
            }
        },
        Headers = new Dictionary<string, string>
        {
            ["X-Custom-Header"] = "value"
        },
        Metadata = new Dictionary<string, string>
        {
            ["category"] = "transactional",
            ["user_id"] = "12345"
        }
    };

    var result = await _emailProvider.SendAsync(message);
}
```

### Templated Emails

```csharp
public async Task SendPasswordResetAsync(string email, string userName, string resetToken)
{
    var provider = _serviceProvider.GetRequiredService<FlexSmtpTemplatedEmailProvider>();

    await provider.SendTemplatedAsync(
        to: email,
        templateId: "password-reset",
        templateData: new Dictionary<string, object>
        {
            ["userName"] = userName,
            ["resetUrl"] = $"https://app.example.com/reset?token={resetToken}",
            ["expiryHours"] = 24,
            ["companyName"] = "Acme Corp"
        }
    );
}

// Custom template
public async Task SendCustomTemplateAsync()
{
    var engine = _serviceProvider.GetRequiredService<IFlexEmailTemplateEngine>();

    engine.RegisterTemplate(new FlexEmailTemplate
    {
        Id = "order-shipped",
        Name = "Order Shipped",
        Subject = "Your order {{orderId}} has shipped!",
        IsHtml = true,
        Body = """
            <h2>Good news, {{userName}}!</h2>
            <p>Your order #{{orderId}} has been shipped.</p>
            <p>Tracking: <a href="{{trackingUrl}}">{{trackingNumber}}</a></p>
            """
    });

    await provider.SendTemplatedAsync(
        to: "customer@email.com",
        templateId: "order-shipped",
        templateData: new Dictionary<string, object>
        {
            ["userName"] = "John",
            ["orderId"] = "ORD-12345",
            ["trackingNumber"] = "1Z999AA10123456784",
            ["trackingUrl"] = "https://track.example.com/..."
        }
    );
}
```

### Bulk Emails

```csharp
// Send same message to multiple recipients
public async Task SendNewsletterAsync(List<string> subscribers)
{
    var result = await _emailProvider.SendToManyAsync(
        recipients: subscribers,
        subject: "Monthly Newsletter",
        body: "<h1>This Month's Updates</h1>...",
        isHtml: true
    );

    _logger.LogInformation(
        "Newsletter sent to {SuccessCount}/{TotalCount} subscribers",
        result.SuccessCount,
        result.TotalCount
    );

    // Log failures
    foreach (var failure in result.Results.Where(r => !r.IsSuccess))
    {
        _logger.LogWarning(
            "Failed to send to {Recipient}: {Error}",
            failure.Recipient,
            failure.ErrorMessage
        );
    }
}

// Send different messages
public async Task SendPersonalizedEmailsAsync(List<User> users)
{
    var messages = users.Select(user => new FlexEmailMessage
    {
        To = new List<string> { user.Email },
        Subject = $"Hello {user.Name}!",
        HtmlBody = $"<p>Personalized content for {user.Name}</p>"
    });

    var result = await _emailProvider.SendBulkAsync(messages);
}
```

### Attachments

```csharp
// File attachment
public async Task SendWithAttachmentAsync(string to, string filePath)
{
    var fileBytes = await File.ReadAllBytesAsync(filePath);
    
    var message = new FlexEmailMessage
    {
        To = new List<string> { to },
        Subject = "Document Attached",
        TextBody = "Please find the attached document.",
        Attachments = new List<FlexEmailAttachment>
        {
            new FlexEmailAttachment
            {
                FileName = Path.GetFileName(filePath),
                ContentType = "application/pdf",
                Content = fileBytes
            }
        }
    };

    await _emailProvider.SendAsync(message);
}

// Inline image
public async Task SendWithInlineImageAsync(string to)
{
    var logoBytes = await File.ReadAllBytesAsync("logo.png");
    
    var message = new FlexEmailMessage
    {
        To = new List<string> { to },
        Subject = "Email with Logo",
        HtmlBody = """
            <html>
            <body>
                <img src="cid:logo" alt="Company Logo" />
                <p>Email content here</p>
            </body>
            </html>
            """,
        Attachments = new List<FlexEmailAttachment>
        {
            new FlexEmailAttachment
            {
                FileName = "logo.png",
                ContentType = "image/png",
                Content = logoBytes,
                IsInline = true,
                ContentId = "logo"  // Referenced in HTML as cid:logo
            }
        }
    };

    await _emailProvider.SendAsync(message);
}
```

## Key Points to Consider

### Best Practices

1. **Use Templates** - Define templates for common emails (welcome, reset, etc.)
2. **Validate Emails** - Use `ValidateEmailFormat()` before sending
3. **Handle Errors** - Always check `IsSuccess` and log failures
4. **Use Console in Dev** - Avoid sending real emails during development
5. **Include Text Fallback** - Set both `HtmlBody` and `TextBody`
6. **Test Connection** - Use `TestConnectionAsync()` on startup
7. **Monitor Delivery** - Track `MessageId` for delivery status

### Email Validation

```csharp
public async Task<bool> SendIfValidAsync(string email, string subject, string body)
{
    if (!_emailProvider.ValidateEmailFormat(email))
    {
        _logger.LogWarning("Invalid email format: {Email}", email);
        return false;
    }

    var result = await _emailProvider.SendAsync(email, subject, body);
    return result.IsSuccess;
}
```

### Error Handling

```csharp
public async Task<bool> SendWithRetryAsync(FlexEmailMessage message, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            var result = await _emailProvider.SendAsync(message);
            
            if (result.IsSuccess)
            {
                return true;
            }

            // Handle specific error codes
            switch (result.ErrorCode)
            {
                case "INVALID_RECIPIENT":
                    _logger.LogError("Invalid recipient, not retrying");
                    return false;
                
                case "RATE_LIMITED":
                    _logger.LogWarning("Rate limited, waiting before retry");
                    await Task.Delay(TimeSpan.FromSeconds(5 * attempt));
                    break;
                
                default:
                    _logger.LogWarning(
                        "Send failed (attempt {Attempt}/{Max}): {Error}",
                        attempt, maxRetries, result.ErrorMessage
                    );
                    break;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error sending email");
            
            if (attempt == maxRetries)
                throw;
            
            await Task.Delay(TimeSpan.FromSeconds(2 * attempt));
        }
    }

    return false;
}
```

### Testing Connection

```csharp
// In startup/health check
public async Task<bool> VerifyEmailServiceAsync()
{
    var isConnected = await _emailProvider.TestConnectionAsync();
    
    if (!isConnected)
    {
        _logger.LogError("Email provider is not configured correctly");
        return false;
    }

    _logger.LogInformation("Email provider connected: {Provider}", 
        _emailProvider.ProviderName);
    
    return true;
}
```

### Multiple Providers

```csharp
// Register multiple email providers
builder.Services.AddKeyedSingleton<IFlexEmailProvider>("transactional", sp =>
    new FlexSendGridEmailProvider(new FlexSendGridEmailOptions
    {
        ApiKey = Configuration["SendGrid:ApiKey"]!,
        DefaultFrom = "transactions@company.com"
    }));

builder.Services.AddKeyedSingleton<IFlexEmailProvider>("marketing", sp =>
    new FlexSendGridEmailProvider(new FlexSendGridEmailOptions
    {
        ApiKey = Configuration["SendGrid:MarketingApiKey"]!,
        DefaultFrom = "marketing@company.com"
    }));

// Use in service
public class EmailService
{
    private readonly IFlexEmailProvider _transactional;
    private readonly IFlexEmailProvider _marketing;

    public EmailService(
        [FromKeyedServices("transactional")] IFlexEmailProvider transactional,
        [FromKeyedServices("marketing")] IFlexEmailProvider marketing)
    {
        _transactional = transactional;
        _marketing = marketing;
    }

    public async Task SendOrderConfirmationAsync(string email, Order order)
    {
        await _transactional.SendAsync(email, "Order Confirmed", ...);
    }

    public async Task SendNewsletterAsync(List<string> subscribers)
    {
        await _marketing.SendToManyAsync(subscribers, "Newsletter", ...);
    }
}
```

## Examples

### Complete Email Service

```csharp
public class EmailService : IEmailService
{
    private readonly IFlexEmailProvider _emailProvider;
    private readonly ILogger<EmailService> _logger;

    public EmailService(
        IFlexEmailProvider emailProvider,
        ILogger<EmailService> logger)
    {
        _emailProvider = emailProvider;
        _logger = logger;
    }

    public async Task<bool> SendWelcomeEmailAsync(User user)
    {
        try
        {
            var result = await _emailProvider.SendTemplatedAsync(
                to: user.Email,
                templateId: "welcome",
                templateData: new Dictionary<string, object>
                {
                    ["userName"] = user.Name,
                    ["loginUrl"] = "https://app.example.com/login",
                    ["companyName"] = "Acme Corp"
                }
            );

            if (result.IsSuccess)
            {
                _logger.LogInformation(
                    "Welcome email sent to {Email} (MessageId: {MessageId})",
                    user.Email, result.MessageId
                );
                return true;
            }

            _logger.LogError(
                "Failed to send welcome email to {Email}: {Error}",
                user.Email, result.ErrorMessage
            );
            return false;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error sending welcome email to {Email}", user.Email);
            return false;
        }
    }

    public async Task<bool> SendPasswordResetAsync(string email, string resetToken)
    {
        var resetUrl = $"https://app.example.com/reset-password?token={resetToken}";
        
        var result = await _emailProvider.SendTemplatedAsync(
            to: email,
            templateId: "password-reset",
            templateData: new Dictionary<string, object>
            {
                ["resetUrl"] = resetUrl,
                ["expiryHours"] = 24
            }
        );

        return result.IsSuccess;
    }
}
```

## Testing

```csharp
public class EmailServiceTests
{
    [Fact]
    public async Task SendAsync_WithValidEmail_Succeeds()
    {
        // Arrange
        var provider = new FlexConsoleEmailProvider(Options.Create(new FlexConsoleEmailOptions()));

        // Act
        var result = await provider.SendAsync(
            "test@example.com",
            "Test Subject",
            "Test Body"
        );

        // Assert
        Assert.True(result.IsSuccess);
        Assert.NotNull(result.MessageId);
    }

    [Fact]
    public async Task ValidateEmailFormat_WithInvalidEmail_ReturnsFalse()
    {
        // Arrange
        var provider = new FlexConsoleEmailProvider(Options.Create(new FlexConsoleEmailOptions()));

        // Act
        var isValid = provider.ValidateEmailFormat("invalid-email");

        // Assert
        Assert.False(isValid);
    }
}
```

## See Also

* [Message Providers](https://docs.flexbase.in/data-and-providers/providers/text-message) - SMS and push notifications
* [FlexEmail Provider Reference](https://github.com/sumeru-flexbase/flexbase-docs/blob/main/ReferenceDocs/README_FlexEmailProvider.md) - Detailed API documentation
* [Email Provider Guide](https://github.com/sumeru-flexbase/flexbase-docs/blob/main/ReferenceDocs/FlexEmailProvider_Guide.md) - Advanced features
