@nestjs-modules/mailer Documentation

repository·main·Indexed 21 days ago

https://github.com/nest-modules/mailer

A NestJS module for sending emails built on top of Nodemailer. It supports multiple transporters, CSS inlining, email previews, and various template engines including Handlebars, Pug, and EJS. It provides synchronous and asynchronous configuration via MailerModule and allows for custom template adapters and runtime registration of multiple SMTP transporters for failover or categorization.

Tokens
29.1K
Snippets
100
Records
116
Agent score
74%

What's inside @nestjs-modules/mailer

  1. Implement a custom template adapter

    main

    To use a template engine other than the ones officially provided, you must create a class that implements the TemplateAdapter interface. The core method to implement is compile(mail, callback, options), where you handle the template loading and rendering logic, eventually passing the rendered HTML back via the callback.

    import { MailerOptions, TemplateAdapter } from '@nestjs-modules/mailer';
    
    export class MyCustomAdapter implements TemplateAdapter {
      compile(
        mail: any,
        callback: (err?: any, body?: string) => any,
        options: MailerOptions,
      ): void {
        // 1. Extract template info from mail.data.template
        // 2. Render template using your engine
        // 3. Call callback(null, renderedHtml) or callback(error)
      }
    }
  2. Understand i18n fallback behavior

    main

    The behavior of missing templates depends on the fallback setting in your i18n configuration:

    • fallback: true (Default): If a template is requested for a specific locale (e.g., fr) but does not exist in that locale's directory, the system will automatically attempt to use the template from the defaultLocale directory.
    • fallback: false: The system will throw an error if the template for the requested locale is missing.

    Note: If no locale is provided in the sendMail() call, the template is resolved normally without any i18n directory prefix.

  3. How Template Adapters work

    main
    Template Adapters wrap a specific template engine (like Handlebars, Pug, or EJS) to provide a consistent interface for compiling email templates within the MailerModule. You configure an adapter by passing an instance of it to the template.adapter property in the MailerModule.forRoot() configuration.
  4. Configure and use multiple transporters

    main

    You can define multiple SMTP servers or transport types (SMTP, SES, Sendmail, Stream, JSON, or custom) by using the transports key in forRoot(). Each transporter is assigned a unique name. To use a specific transporter when sending an email, pass the transporterName property in the sendMail options.

    // Configuration
    MailerModule.forRoot({
      transports: {
        primary: {
          host: 'smtp.example.com',
          port: 587,
          auth: { user: 'primary@example.com', pass: 'pass1' },
        },
        secondary: {
          host: 'smtp.other.com',
          port: 587,
          auth: { user: 'secondary@other.com', pass: 'pass2' },
        },
        ses: {
          SES: { /* AWS SES config */ },
        },
      },
      defaults: {
        from: '"No Reply" <noreply@example.com>',
      },
      template: {
        dir: __dirname + '/templates',
        adapter: new HandlebarsAdapter(),
      },
    })
    
    // Usage
    await this.mailerService.sendMail({
      transporterName: 'secondary',
      to: 'user@example.com',
      subject: 'Hello',
      html: '<b>Hello</b>',
    });
  5. How the TemplateResolver lifecycle works

    main

    The TemplateResolver is triggered during the mail sending process following these steps:

    1. sendMail() is called with a template name.
    2. If a resolver is configured and no explicit html is provided in the mail options, the resolver is invoked.
    3. The resolver fetches the template content based on the templateName and optional context.
    4. The returned content is assigned as the html for the email.
    5. If the resolved template contains metadata.subject and no subject was explicitly provided in the sendMail call, the metadata subject is applied automatically.
  6. Organize templates for Internationalization

    main

    When using i18n, organize your template files into subdirectories named after the locales. The directory structure is determined by your templateDirPattern.

    By default, using templateDirPattern: '{{locale}}/' expects a structure like this:

    templates/
      en/
        welcome.hbs
        reset-password.hbs
      es/
        welcome.hbs
        reset-password.hbs
      fr/
        welcome.hbs
  7. Setup the Mailer Event System

    main

    The Mailer module can react to email lifecycle events using @nestjs/event-emitter. This system is optional; if the package is not installed, events are silently skipped with zero overhead.

    To enable events, install @nestjs/event-emitter and register EventEmitterModule.forRoot() in your application module alongside MailerModule.

    pnpm add @nestjs/event-emitter
    import { EventEmitterModule } from '@nestjs/event-emitter';
    
    @Module({
      imports: [
        EventEmitterModule.forRoot(),
        MailerModule.forRoot({ ... }),
      ],
    })
    export class AppModule {}
  8. Send multiple emails with MailerBatchService

    main

    Use MailerBatchService to send a collection of emails efficiently with built-in concurrency control. This service is ideal for tasks like newsletters where you need to manage how many emails are being sent simultaneously to avoid overwhelming your SMTP server or hitting rate limits.

    To use it, inject MailerBatchService into your provider and call the sendBatch method with an array of messages and a concurrency limit.

    import { MailerBatchService } from '@nestjs-modules/mailer';
    
    @Injectable()
    export class NewsletterService {
      constructor(private readonly batchService: MailerBatchService) {}
    
      async sendNewsletter(subscribers: string[]) {
        const result = await this.batchService.sendBatch({
          messages: subscribers.map((email) => ({
            to: email,
            subject: 'Weekly Newsletter',
            template: 'newsletter',
            context: { email },
          })),
          concurrency: 10,
        });
    
        console.log(`Sent: ${result.sent}, Failed: ${result.failed}`);
        return result;
      }
    }
  9. Configure multiple SMTP transporters

    main

    To use multiple email providers, define a primary transporter in the MailerModule.forRoot() configuration and register additional transporters at runtime using the MailerService.addTransporter() method. This allows you to separate transactional and marketing emails or implement failover mechanisms.

    1. Set the default transporter: Use MailerModule.forRoot() to define the base configuration and the primary transport object.
    2. Register additional transporters: Inject MailerService into a provider and call addTransporter(name, transportOptions) to register named transporters.
    // 1. Define the default transporter in your module
    MailerModule.forRoot({
      defaults: {
        from: '"App" <noreply@example.com>',
      },
      transport: {
        host: 'smtp.primary.com',
        port: 587,
        auth: {
          user: 'primary@example.com',
          pass: 'password',
        },
      },
    });
    
    // 2. Register additional transporters at runtime
    @Injectable()
    export class EmailService {
      constructor(private readonly mailerService: MailerService) {
        this.mailerService.addTransporter('marketing', {
          host: 'smtp.marketing.com',
          port: 587,
          auth: {
            user: 'marketing@example.com',
            pass: 'password',
          },
        });
    
        this.mailerService.addTransporter('backup', {
          host: 'smtp.backup.com',
          port: 587,
          auth: {
            user: 'backup@example.com',
            pass: 'password',
          },
        });
      }
    }
  10. Configure MailerModule with a custom adapter

    main

    When initializing the MailerModule using forRoot, you can provide a custom adapter instance within the template configuration object. This allows the MailerService to use your custom engine for rendering templates specified in sendMail() calls.

    import { Module } from '@nestjs/common';
    import { MailerModule } from '@nestjs-modules/mailer';
    import { TwingAdapter } from './adapters/twing.adapter';
    
    @Module({
      imports: [
        MailerModule.forRoot({
          transport: {
            host: 'smtp.example.com',
            port: 587,
            secure: false,
            auth: {
              user: "username",
              pass: "password",
            },
          },
          defaults: {
            from:'"nest-modules" <modules@nestjs.com>',
          },
          template: {
            dir: `${process.cwd()}/templates/`,
            adapter: new TwingAdapter(),
          },
        }),
      ],
    })
    export class AppModule {}
  11. Implement a Custom Transport Factory

    main

    If you need to customize how transporters are created, implement the MailerTransportFactory interface. This allows you to inject custom logic into the transport creation process, such as using specific nodemailer configurations or dynamic settings.

    To use your custom factory, register it using the MAILER_TRANSPORT_FACTORY provider within the extraProviders array of MailerModule.forRootAsync().

    import { MailerTransportFactory, MAILER_TRANSPORT_FACTORY } from '@nestjs-modules/mailer';
    import { createTransport } from 'nodemailer';
    
    // 1. Implement the interface
    export class CustomTransportFactory implements MailerTransportFactory {
      createTransport(options?: any) {
        return createTransport({
          // your custom transport configuration
        });
      }
    }
    
    // 2. Register via forRootAsync
    MailerModule.forRootAsync({
      useFactory: () => ({
        transport: { host: 'smtp.example.com', port: 587 },
      }),
      extraProviders: [
        {
          provide: MAILER_TRANSPORT_FACTORY,
          useClass: CustomTransportFactory,
        },
      ],
    })