DEV Community

Stanislav Karol
Stanislav Karol

Posted on

6 1

Отправка писем в NestJS используя nodemailer. Публикация скриптов.

Мне понадобилось отправить сообщение по почте о подтверждении регистрации на сайте. На текущий момент проверенное, хорошо зарекомендовавшее себя решение это Nodemailer. В роли бек-сервера у меня NestJS. Для NestJS есть модуль Nest JS Mailer, вот с ним и решил работать. Сперва установим пакеты @nestjs-modules/mailer, nodemailer и в dev-зависимость будет не лишним поставить @types/nodemailer . В качестве сервера почты я решил воспользоваться услугами яндекс.

Установка модуля

После того, как получена учётная запись от яндекса, записал в файл .env такие переменные:
MAIL_TRANSPORT строка подключения к smtp-серверу для отправки почты (см. https://nodemailer.com/smtp/)
MAIL_FROM_NAME от чьего имени будет приходить почта.

Пример:

MAIL_TRANSPORT=smtps://useYandex@yandex.ru:password@smtp.yandex.ru
MAIL_FROM_NAME=WebWork
Enter fullscreen mode Exit fullscreen mode

Написал в файле src/configs/mail.config.ts формирование настройки почтовой службы:

import { ConfigService } from '@nestjs/config';
import { EjsAdapter } from '@nestjs-modules/mailer/dist/adapters/ejs.adapter';

export const getMailConfig = async (
  configService: ConfigService,
): Promise<any> => {
  const transport = configService.get<string>('MAIL_TRANSPORT');
  const mailFromName = configService.get<string>('MAIL_FROM_NAME');
  const mailFromAddress = transport.split(':')[1].split('//')[1];

  return {
    transport,
    defaults: {
      from: `"${mailFromName}" <${mailFromAddress}>`,
    },
    template: {
      adapter: new EjsAdapter(),
      options: {
        strict: false,
      },
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Что я здесь задаю: defaults.from - задаётся имя/адрес отправителя, template - у меня было больше практики с ejs, поэтому выбран такой формат для шаблонов писем.
Завершающий шаг установки - подключение модуля почты в приложение:

import { MailerModule } from '@nestjs-modules/mailer';

@Module({
  imports: [

    MailerModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: getMailConfig,
    }),

  ],
})

Enter fullscreen mode Exit fullscreen mode

Отправка почты

У меня уже есть шаблон почты в файле confirmReg.ejs:

<main>
  Доброго дня, <%= locals.username %>!<br />
  От Вас поступила регистрация на сайте post-life.<br />
  Для окончания регистрации пройдите пожалуйста по
  <a href="<%= urlConfirmAddress %><%= id %>">ссылке</a> .
</main>
Enter fullscreen mode Exit fullscreen mode

Вот как он используется для отправки почты:

import { MailerService } from '@nestjs-modules/mailer';
Enter fullscreen mode Exit fullscreen mode
@Injectable()
export class UserService {
  constructor(
    // помимо всего прочего подключение сервиса отправки
    private readonly mailerService: MailerService,
  ) {}
///
}
Enter fullscreen mode Exit fullscreen mode
  async sendConfirmMail(user: UserEntity) {
    const urlConfirmAddress = this.configService.get<string>(
      'URL_CONFIRM_ADDRESS',
    );
    // Отправка почты
    return await this.mailerService
      .sendMail({
        to: user.email,
        subject: 'Подтверждение регистрации',
        template: join(__dirname, '/../templates', 'confirmReg'),
        context: {
          id: user.id,
          username: user.username,
          urlConfirmAddress,
        },
      })
      .catch((e) => {
        throw new HttpException(
          `Ошибка работы почты: ${JSON.stringify(e)}`,
          HttpStatus.UNPROCESSABLE_ENTITY,
        );
      });
  }
Enter fullscreen mode Exit fullscreen mode

Публикация скриптов

Описываемый проект после публикации и проверки отправки почты выдал ошибку о том, что не может найти файл confirmReg.ejs . Почему шаблоны писем потерялись? - Компилятор из TS в JS "не заметил" файлы с раcширением ejs и после билда проекта каталог src/templates не оказался в папке назначения dist . Мои поиски решения привели меня вот на эту страницу . Суть того, что можно оттуда вынести: у NestJS есть свои опции настройки для typescript и в них можно указать файлы, которые ts должен скопировать "как есть" в каталог назначения. Теперь nest-cli.json стал таким:

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": [
      "**/*.ejs"
    ],
    "watchAssets": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Ссылка на проект : https://github.com/SLKarol/post-life

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (2)

Collapse
 
itsjokexd profile image
itsjokexd • Edited

 Привет! Сейчас как раз занимаюсь проектом, где нужно отправлять юзерам при регистрации письма. Делал как у вас, но столкнулся с проблемой, что Яндекс не даёт слать письма из приложения. Просит решить капчу, а у меня нет никакого интерфейса, где эта капча могла бы появиться.
Возникала ли у вас такая проблема? Если да, то как боролись?

Collapse
 
slkarol profile image
Stanislav Karol • Edited

Приветствую!
Что-то похожее было- мне бот яндекса написал, что имя отправителя должно совпадать с именем яндекс-учётки. То есть у меня там было "Бот address@yandex.ru" - пришлось изменить на соответствие рег. данным.
Второй отказ был такой: Яндекс-бот пишет мне, что почта довольно подозрительная, похоже на спам. Предлагает мне для снятия подозрений отправить письмо с этого адреса. Когда отправил, почта отправлялась без проблем, но не долго. Потом опять яндекс-бот пишет о подозрении на спам, опять нужно руками какое-то письмо отправить.
Поэтому я решил отойти от яндекса- может гугл или другой сервис попробовать.

Надеюсь мои два шага Вам помогут.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs