npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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:

  1. Attempts user SMTP with the configured retry settings (maxRetries, baseDelay, maxDelay)
  2. Retries on transient errors: ECONNREFUSED, ENOTFOUND, ETIMEDOUT, ECONNRESET
  3. Falls back to system providers if all retries fail
  4. Sets user's email as replyTo on 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-primary

Real-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