# 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](/data-and-providers/providers/text-message.md) - 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flexbase.in/data-and-providers/providers/e-mail.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
