MailerService

MailerService implementation in NestJS
import {Injectable, Logger} from '@nestjs/common';
import {ConfigService} from '@nestjs/config';
import {createTransport} from 'nodemailer';
import * as Mail from 'nodemailer/lib/mailer';

import {isDevEnv} from '@/config/environment-utils';

import {
  config,
  getChangeMailTemplate,
  getChangePasswordTemplate,
  getConfirmMailTemplate,
  getResetPasswordTemplate,
} from './templates';

@Injectable()
export class MailerService {
  private transporter: Mail;
  private logger = new Logger(MailerService.name);

  constructor(private readonly configService: ConfigService) {
    this.transporter = createTransport({
      host: this.configService.get('MAIL_HOST'),
      port: this.configService.get('MAIL_PORT'),
      secure: this.configService.get('MAIL_SECURE'),
      auth: {
        user: this.configService.get('MAIL_USER'),
        pass: this.configService.get('MAIL_PASS'),
      },
    });
  }

  async sendVerifyEmailMail(email: string, token: string): Promise<boolean> {
    const buttonLink = `${config.project.mailVerificationUrl}?token=${token}`;
    const mailContent = getConfirmMailTemplate(buttonLink);

    const mailOptions = {
      from: `"${config.sender.name}" <${config.sender.email}>`,
      to: email,
      subject: `Welcome to ${config.project.name}! Confirm your email`,
      html: mailContent,
    };

    return this.sendMail(mailOptions);
  }

  async sendChangeEmailMail(
    oldEmail: string,
    newEmail: string,
    token: string
  ): Promise<boolean> {
    const buttonLink = `${config.project.mailChangeUrl}?token=${token}`;
    const mailContent = getChangeMailTemplate(buttonLink, newEmail);

    const mailOptions = {
      from: `"${config.sender.name}" <${config.sender.email}>`,
      to: oldEmail,
      subject: `Change your ${config.project.name} account email`,
      html: mailContent,
    };

    return this.sendMail(mailOptions);
  }

  async sendResetPasswordMail(email: string, token: string): Promise<boolean> {
    const buttonLink = `${config.project.resetPasswordUrl}?token=${token}`;
    const mailContent = getResetPasswordTemplate(buttonLink);

    const mailOptions = {
      from: `"${config.sender.name}" <${config.sender.email}>`,
      to: email,
      subject: `Reset your ${config.project.name} account Password`,
      html: mailContent,
    };

    return this.sendMail(mailOptions);
  }

  async sendPasswordChangeInfoMail(email: string): Promise<boolean> {
    const mailContent = getChangePasswordTemplate();

    const mailOptions = {
      from: `"${config.sender.name}" <${config.sender.email}>`,
      to: email,
      subject: `Your ${config.project.name} account Password has changed`,
      html: mailContent,
    };

    return this.sendMail(mailOptions);
  }

  async sendMail(mailOptions) {
    const env = this.configService.get('NODE_ENV');

    if (isDevEnv(env)) {
      Logger.log('Attaching mail contents:');
      console.log(mailOptions);
      return false;
    }

    return new Promise<boolean>((resolve) => {
      return this.transporter.sendMail(mailOptions, async (error) => {
        if (error) {
          console.log(error);
          this.logger.warn(
            'Mail sending failed, check your service credentials.'
          );
          resolve(false);
        }
        resolve(true);
      });
    });
  }
}
export const config = {
  project: {
    name: 'project',
    address: 'project.com',
    logoUrl: 'https://project.com/avatar.png',
    color: '#123456',
    url: 'http://localhost:4200',
    mailVerificationUrl: 'http://localhost:3000/auth/verify',
    mailChangeUrl: 'http://localhost:3000/auth/change-email',
    resetPasswordUrl: 'http://localhost:4200/reset-password',
    socials: [
      {name: 'Twitter', url: 'https://twitter.com/--project-handler--'},
    ],
  },
  sender: {
    name: 'hi',
    email: 'hi@hi.com',
  },
} as const;

export type MailConfig = typeof config;