@maxrent/strapi-provider-email-multi
v5.42.1
Published
Multi-provider email for Strapi (Brevo API + SMTP with failover)
Downloads
148
Readme
Strapi Provider Email Multi
Multi-provider email for Strapi v5 with support for Brevo API and SMTP, featuring automatic failover, per-provider defaults, retry logic, and end-user SMTP sending.
Features
- Multi-Provider Support: Brevo API + SMTP with priority-based failover
- Single or Multiple Providers: Works with one provider or many
- Per-Provider Defaults: Each provider can have its own sender settings
- Automatic Failover: Switch to backup provider on failures
- Retry Logic: Exponential backoff with configurable retries
- Send As User: Send emails using end-user's own SMTP credentials
- Health Monitoring: Check status of all providers
- Statistics Tracking: Monitor sent/failed counts per provider
- Debug Logging: Detailed logs for troubleshooting
Installation
This is a local provider. Located at src/providers/strapi-provider-email-multi/.
Quick Start - Single Provider
For a simple setup with just one email provider:
// config/plugins.ts
export default ({ env }) => ({
email: {
config: {
provider: "strapi-provider-email-multi",
providerOptions: {
providers: [
{
id: "brevo-main",
type: "brevo",
priority: 1,
apiKey: env("BREVO_API_KEY"),
},
],
},
settings: {
defaultFrom: env("EMAIL_USERNAME", "[email protected]"),
defaultSenderName: "Your Company",
},
},
},
});Full Configuration Example
// config/plugins.ts
export default ({ env }) => ({
email: {
config: {
// Provider name (must match folder name in src/providers/)
provider: "strapi-provider-email-multi",
providerOptions: {
// ═══════════════════════════════════════════════════════════════════
// PROVIDERS - Define multiple email providers with failover
// ═══════════════════════════════════════════════════════════════════
providers: [
// ─────────────────────────────────────────────────────────────────
// PRIMARY PROVIDER: Brevo API
// ─────────────────────────────────────────────────────────────────
{
// Unique identifier for this provider (used in logs/stats)
id: "brevo-primary",
// Provider type: 'brevo' for Brevo API, 'smtp' for SMTP server
type: "brevo",
// Priority order: lower number = higher priority (tried first)
// Example: priority 1 is tried before priority 2
priority: 1,
// Brevo API key from https://app.brevo.com/settings/keys/api
apiKey: env("BREVO_API_KEY"),
// Request timeout in milliseconds (default: 30000)
// Increase if experiencing timeout errors
timeout: 30000,
// Enable/disable this provider (default: true)
// Set to false to temporarily disable without removing config
enabled: true,
// ── Per-Provider Sender Defaults ──
// These override the global settings for this provider only
// Default sender email address
defaultFrom: env("BREVO_SENDER_EMAIL", "[email protected]"),
// Default sender display name (shown in email clients)
defaultSenderName: env("BREVO_SENDER_NAME", "Your Company"),
// Default reply-to address (where replies are sent)
defaultReplyTo: env("BREVO_REPLY_EMAIL", "[email protected]"),
},
// ─────────────────────────────────────────────────────────────────
// BACKUP PROVIDER: SMTP Server
// Used when primary provider fails (rate limit, timeout, etc.)
// ─────────────────────────────────────────────────────────────────
{
id: "smtp-backup",
type: "smtp",
priority: 2, // Lower priority = used as backup
// SMTP server hostname
host: env("EMAIL_HOST"),
// SMTP port (common: 25, 465 (SSL), 587 (STARTTLS))
port: parseInt(env("EMAIL_PORT", "465"), 10),
// Use SSL/TLS connection
// true = port 465 (implicit SSL)
// false = port 587 (STARTTLS) or port 25 (plain)
secure: true,
// SMTP authentication credentials
auth: {
user: env("EMAIL_USERNAME"),
pass: env("EMAIL_PASSWORD"),
},
// ── Optional SMTP Settings ──
// Use connection pooling for multiple emails (default: false)
// Enable for high-volume sending
pool: false,
// Maximum concurrent connections when pooling (default: 5)
maxConnections: 5,
// TLS options
tls: {
// Reject connections with invalid SSL certificates (default: true)
// Set to false only for self-signed certs in development
rejectUnauthorized: true,
},
// ── Per-Provider Sender Defaults ──
defaultFrom: env("EMAIL_USERNAME", "[email protected]"),
defaultSenderName: env("EMAIL_SENDER_NAME", "Your Company Notifications"),
defaultReplyTo: env("EMAIL_REPLYTO_EMAIL", "[email protected]"),
},
],
// ═══════════════════════════════════════════════════════════════════
// RETRY CONFIGURATION - How to handle transient failures
// ═══════════════════════════════════════════════════════════════════
retry: {
// Maximum number of retry attempts per provider (default: 3)
// Total attempts = maxRetries (1 initial + retries)
maxRetries: 3,
// Initial delay between retries in milliseconds (default: 1000)
// First retry waits baseDelay, second waits baseDelay * 2, etc.
baseDelay: 1000,
// Maximum delay with exponential backoff (default: 10000)
// Prevents retry delays from growing too large
maxDelay: 10000,
// Retry when rate limited (HTTP 429) (default: true)
// Set to false if you prefer immediate failover on rate limits
retryOnRateLimit: true,
},
// ═══════════════════════════════════════════════════════════════════
// FAILOVER SETTINGS - When to switch to backup provider
// ═══════════════════════════════════════════════════════════════════
// Enable automatic failover to next priority provider (default: true)
failoverEnabled: true,
// Error codes that trigger failover to backup provider
// Other errors will throw immediately without trying backup
failoverOnCodes: [
"RATE_LIMIT", // HTTP 429 - Rate limited by provider
"TIMEOUT", // Request took too long
"NETWORK_ERROR", // Connection refused, DNS failed, etc.
"HTTP_5XX", // Server error (500, 502, 503, etc.)
],
// Remember failed providers temporarily (default: true)
// When true, skips providers that recently failed
rememberFailedProvider: true,
// Reset failed provider status after this many milliseconds (default: 300000 = 5 min)
// After this time, failed provider will be tried again
resetFailedAfter: 300000,
// ═══════════════════════════════════════════════════════════════════
// DEBUG - Enable detailed logging
// ═══════════════════════════════════════════════════════════════════
// Enable debug logging (default: false)
// Shows detailed logs for sends, retries, failovers, health checks
debug: env("NODE_ENV") === "development",
},
// ═══════════════════════════════════════════════════════════════════
// GLOBAL SETTINGS - Fallback defaults if provider doesn't specify
// ═══════════════════════════════════════════════════════════════════
settings: {
// Global default sender email (used if provider has no defaultFrom)
defaultFrom: env("EMAIL_USERNAME", "[email protected]"),
// Global default sender name
defaultSenderName: env("EMAIL_SENDER_NAME", "Your Company"),
// Global default reply-to address
defaultReplyTo: env("EMAIL_REPLYTO_EMAIL", "[email protected]"),
},
},
},
});Configuration Reference
Provider Options
| Option | Type | Default | Description |
| ------------------------ | -------- | ------------ | -------------------------------- |
| providers | array | required | Array of provider configurations |
| retry | object | see below | Retry configuration |
| failoverEnabled | boolean | true | Enable automatic failover |
| failoverOnCodes | string[] | see above | Error codes triggering failover |
| rememberFailedProvider | boolean | true | Skip recently failed providers |
| resetFailedAfter | number | 300000 | Reset failed status after ms |
| debug | boolean | false | Enable debug logging |
Brevo Provider Config
| Option | Type | Required | Default | Description |
| ------------------- | --------- | -------- | ------- | ------------------------------- |
| id | string | ✅ | - | Unique provider identifier |
| type | 'brevo' | ✅ | - | Must be 'brevo' |
| priority | number | ✅ | - | Priority order (lower = higher) |
| apiKey | string | ✅ | - | Brevo API key |
| timeout | number | | 30000 | Request timeout in ms |
| enabled | boolean | | true | Enable/disable provider |
| defaultFrom | string | | - | Default sender email |
| defaultSenderName | string | | - | Default sender name |
| defaultReplyTo | string | | - | Default reply-to address |
SMTP Provider Config
| Option | Type | Required | Default | Description |
| ------------------------ | -------- | -------- | ------- | -------------------------- |
| id | string | ✅ | - | Unique provider identifier |
| type | 'smtp' | ✅ | - | Must be 'smtp' |
| priority | number | ✅ | - | Priority order |
| host | string | ✅ | - | SMTP server hostname |
| port | number | | 587 | SMTP port |
| secure | boolean | | false | Use SSL (true for 465) |
| auth.user | string | ✅ | - | SMTP username |
| auth.pass | string | ✅ | - | SMTP password |
| pool | boolean | | false | Use connection pooling |
| maxConnections | number | | 5 | Max pooled connections |
| tls.rejectUnauthorized | boolean | | true | Reject invalid SSL certs |
| defaultFrom | string | | - | Default sender email |
| defaultSenderName | string | | - | Default sender name |
| defaultReplyTo | string | | - | Default reply-to address |
Retry Config
| Option | Type | Default | Description |
| ------------------ | ------- | ------- | ---------------------- |
| maxRetries | number | 3 | Max retry attempts |
| baseDelay | number | 1000 | Initial delay in ms |
| maxDelay | number | 10000 | Max delay with backoff |
| retryOnRateLimit | boolean | true | Retry on HTTP 429 |
Usage Examples
Basic Email
await strapi.plugin("email").service("email").send({
to: "[email protected]",
subject: "Hello from Strapi",
text: "Plain text content",
html: "<h1>HTML content</h1>",
});Email with Sender Name
await strapi
.plugin("email")
.service("email")
.send({
to: { email: "[email protected]", name: "John Doe" },
from: { email: "[email protected]", name: "My Company" },
subject: "Welcome!",
html: "<p>Welcome to our platform!</p>",
});Multiple Recipients with CC/BCC
await strapi
.plugin("email")
.service("email")
.send({
to: ["[email protected]", { email: "[email protected]", name: "Jane" }],
cc: ["[email protected]"],
bcc: ["[email protected]"],
subject: "Team Update",
html: "<p>Important announcement</p>",
});With Attachments
await strapi
.plugin("email")
.service("email")
.send({
to: "[email protected]",
subject: "Document attached",
html: "<p>Please find the document attached.</p>",
attachment: [
{
name: "document.pdf",
content: "base64-encoded-content-here",
contentType: "application/pdf",
},
],
});Templated Email (with Lodash interpolation)
Use Strapi's built-in sendTemplatedEmail for dynamic content with variable substitution:
// Define template with lodash-style interpolation
const emailTemplate = {
subject: "Welcome to <%= company %>, <%= user.name %>!",
text: `Hello <%= user.name %>,
Thank you for joining <%= company %>. Your account is ready.
Best regards,
The <%= company %> Team`,
html: `
<h1>Welcome, <%= user.name %>!</h1>
<p>Thank you for joining <strong><%= company %></strong>.</p>
<p>Your account is ready to use.</p>
<p>Best regards,<br/>The <%= company %> Team</p>
`,
};
// Data to populate the template
const templateData = {
user: {
name: "John Doe",
email: "[email protected]",
},
company: "Your Company",
confirmationUrl: "https://example.com/confirm/abc123",
};
// Send templated email
await strapi.plugin("email").service("email").sendTemplatedEmail(
{
// Email options
to: templateData.user.email,
from: "[email protected]",
replyTo: "[email protected]",
},
emailTemplate,
templateData,
);Templated Email with sendAs (User SMTP)
Combine templated emails with user's own SMTP server using sendTemplatedEmail:
// Get user's email config from database
const emailAccount = await strapi.documents("api::email-account.email-account").findFirst({
filters: { owner: ownerId },
});
// Define the template
const emailTemplate = {
subject: "Booking Confirmation - <%= propertyName %>",
text: `Dear <%= guest.name %>,\n\nYour booking at <%= propertyName %> is confirmed.\n\nCheck-in: <%= checkIn %>\nCheck-out: <%= checkOut %>\n\nBest regards,\n<%= host.name %>`,
html: `
<h1>Booking Confirmed!</h1>
<p>Dear <strong><%= guest.name %></strong>,</p>
<p>Your booking at <strong><%= propertyName %></strong> is confirmed.</p>
<ul>
<li>Check-in: <%= checkIn %></li>
<li>Check-out: <%= checkOut %></li>
</ul>
<p>Best regards,<br/><%= host.name %></p>
`,
};
// Template data
const templateData = {
guest: { name: "John Doe", email: guestEmail },
propertyName: "Beachfront Villa",
checkIn: "2026-03-15",
checkOut: "2026-03-20",
host: { name: emailAccount.senderName },
};
// sendAs is passed through emailOptions to the provider
await strapi
.plugin("email")
.service("email")
.sendTemplatedEmail(
{
to: guestEmail,
// sendAs works with sendTemplatedEmail - passed to provider.send()
sendAs: {
host: emailAccount.smtpHost,
port: emailAccount.smtpPort,
secure: emailAccount.smtpPort === 465,
auth: {
user: emailAccount.email,
pass: emailAccount.password,
},
from: emailAccount.email,
senderName: emailAccount.senderName,
replyTo: emailAccount.email,
},
},
emailTemplate,
templateData,
);Send As User (End-User SMTP)
Send emails using the end-user's own SMTP credentials (e.g., property owner sending to guests from their own domain).
Basic Usage
await strapi
.plugin("email")
.service("email")
.send({
to: "[email protected]",
subject: "Message from your host",
html: "<p>Hello from the property owner!</p>",
// Send using owner's own email server
sendAs: {
// ── SMTP Server Settings ──
host: "smtp.owner-domain.com", // Owner's SMTP hostname
port: 465, // SMTP port (465=SSL, 587=STARTTLS)
secure: true, // true for port 465
// ── Authentication ──
auth: {
user: "[email protected]",
pass: "their-password",
},
// ── Sender Details ──
from: "[email protected]", // Sender email address
senderName: "Property Owner", // Display name
replyTo: "[email protected]", // Reply-to address
},
});sendAs Configuration
| Option | Type | Required | Description |
| ------------ | ------------- | -------- | ----------------------------- |
| host | string | ✅ | User's SMTP server hostname |
| port | number | | SMTP port (default: 587) |
| secure | boolean | | Use SSL (true=465, false=587) |
| auth.user | string | ✅ | SMTP username |
| auth.pass | string | ✅ | SMTP password |
| from | string/object | ✅ | Sender email address |
| senderName | string | | Sender display name |
| replyTo | string/object | | Reply-to address |
Retry and Fallback Behavior
When using sendAs, the provider:
- Attempts user SMTP with the configured retry settings (
maxRetries,baseDelay,maxDelay) - Retries on transient errors:
ECONNREFUSED,ENOTFOUND,ETIMEDOUT,ECONNRESET - Falls back to system providers if all retries fail
- Sets user's email as
replyToon fallback so replies go to the owner
Debug log example:
[Orchestrator] 👤 Sending as user via smtp.owner.com:587
[Orchestrator] 👤 ⚠️ User SMTP attempt 1/3 failed: Connection refused
[Orchestrator] 👤 🔄 Retrying in 1000ms...
[Orchestrator] 👤 ❌ User SMTP failed after 3 attempts, falling back to system providers
[Orchestrator] 👤 ✅ Fallback successful via brevo-primaryReal-World Example: Property Owner Messaging Guest
// Get owner's email settings from database
const emailAccount = await strapi.documents("api::email-account.email-account").findFirst({
filters: { owner: ownerId },
});
// Send using owner's SMTP
const result = await strapi
.plugin("email")
.service("email")
.send({
to: guestEmail,
subject: `Message from ${propertyName}`,
html: messageTemplate,
sendAs: {
host: emailAccount.smtpHost,
port: emailAccount.smtpPort,
secure: emailAccount.smtpPort === 465,
auth: {
user: emailAccount.email,
pass: emailAccount.password, // Encrypted in DB, decrypted here
},
from: emailAccount.email,
senderName: emailAccount.senderName || propertyName,
replyTo: emailAccount.email,
},
});
if (result.failedOver) {
// Email was sent via system provider (Brevo/SMTP)
// Guest will see replies go to owner (via replyTo)
console.log("Used fallback provider:", result.provider);
}Advanced Features
Health Check
const emailProvider = strapi.plugin("email").provider;
const health = await emailProvider.healthCheck();
console.log(health);
// [
// { id: 'brevo-primary', type: 'brevo', status: 'healthy', details: {...} },
// { id: 'smtp-backup', type: 'smtp', status: 'healthy' }
// ]Provider Statistics
const stats = strapi.plugin("email").provider.getStats();
console.log(stats);
// {
// providers: [
// { id: 'brevo-primary', type: 'brevo', enabled: true, available: true, sent: 150, failed: 2 },
// { id: 'smtp-backup', type: 'smtp', enabled: true, available: true, sent: 3, failed: 0 }
// ],
// failedOver: 3
// }Dynamic Provider Control
const provider = strapi.plugin("email").provider;
// Disable a provider at runtime
provider.setProviderEnabled("brevo-primary", false);
// Re-enable it
provider.setProviderEnabled("brevo-primary", true);
// Reset failed provider status (allow retrying failed providers)
provider.resetFailedProviders();Check Send Result
const result = await strapi.plugin("email").service("email").send({
to: "[email protected]",
subject: "Test",
html: "<p>Hello</p>",
});
console.log(result);
// {
// messageId: '<[email protected]>',
// provider: 'brevo-primary',
// providerType: 'brevo',
// failedOver: false,
// attemptedProviders: []
// }Error Handling
Error Codes
| Code | Description | Retryable | Triggers Failover |
| ----------------- | ------------------------- | --------- | ----------------- |
| INVALID_EMAIL | Invalid email format | ❌ | ❌ |
| MISSING_CONTENT | No content provided | ❌ | ❌ |
| RATE_LIMIT | Rate limit exceeded (429) | ✅ | ✅ |
| TIMEOUT | Request timeout | ✅ | ✅ |
| NETWORK_ERROR | Connection failed | ✅ | ✅ |
| HTTP_5XX | Server error | ✅ | ✅ |
| INVALID_API_KEY | Invalid API key | ❌ | ❌ |
| SMTP_ERROR | SMTP error | Depends | Depends |
Example Error Handling
try {
const result = await strapi.plugin("email").service("email").send({
to: "[email protected]",
subject: "Test",
html: "<p>Hello</p>",
});
console.log("Sent via:", result.provider);
} catch (error) {
console.error("Error code:", error.code);
console.error("Message:", error.message);
console.error("Attempted providers:", error.attemptedProviders);
if (error.code === "RATE_LIMIT") {
// All providers rate limited - queue for later
}
}Debug Logging
Enable with debug: true in providerOptions.
| Event | Log Prefix | Example |
| ------------------ | ------------------- | --------------------------------------------- |
| Brevo send success | [Brevo] ✅ | Email sent successfully to [email protected] |
| Brevo retry | [Brevo] 🔄 | Retrying in 1000ms... (attempt 2/3) |
| SMTP send success | [SMTP] ✅ | Email sent successfully to [email protected] |
| SMTP retry | [SMTP] 🔄 | Retrying in 1000ms... |
| Orchestrator init | [Orchestrator] 📧 | Initialized with 2 provider(s) |
| Provider failover | [Orchestrator] 🔄 | Failing over to smtp-backup (smtp) |
| User SMTP send | [Orchestrator] 👤 | Sending as user via smtp.owner.com:587 |
| User SMTP fallback | [Orchestrator] 👤 | Fallback successful via brevo-primary |
Environment Variables
# Brevo API
BREVO_API_KEY=your-brevo-api-key
# SMTP Backup
EMAIL_HOST=smtp.example.com
EMAIL_PORT=465
[email protected]
EMAIL_PASSWORD=your-password
# Sender Defaults
EMAIL_SENDER_NAME=Your Company
[email protected]License
MIT
