toonvanstrijp / nestjs-i18n

The i18n module for nestjs.
https://nestjs-i18n.com
Other
643 stars 108 forks source link

NestJS Mailer support #315

Closed DennisSnijder closed 2 years ago

DennisSnijder commented 2 years ago

The NestJS Mailer module has support for using Handlebars/Pugs/EJS. However... this is a different View-engine instance than what is being used to return a view from a controller. This results in the helper functions being unavailable when rendering an email.

NestJS mailer docs: https://nest-modules.github.io/mailer/docs/mailer

DennisSnijder commented 2 years ago

Currently I'm using a workaround to make this work:

I register the MailerModule with .forRootAsync and inject the I18nModule. From there I copied the handlebars helpers implementation from the nestjs-i18n library and implemented them myself using the injected I18nService 😄

toonvanstrijp commented 2 years ago

@DennisSnijder any idea in how we could better incorporate this in the library? So that NestJS mailer works more out of the box?

DennisSnijder commented 2 years ago

@DennisSnijder any idea in how we could better incorporate this in the library? So that NestJS mailer works more out of the box?

@toonvanstrijp I don't think there is an easy solution to this yet...

A hypothetical solution could be to conditionally include the mailer module when it's enabled and/or exists as a dependency and inject a new helper function into the ViewAdapter? Problem is: those ViewAdapters are not publicly exposed from the MailerService 😢.

Perhaps a first step towards an easy out of the box solution is to open a pull request on the mailer module so the ViewAdapter is getting exposed? 🤔

toonvanstrijp commented 2 years ago

@DennisSnijder I took a look at the source code of nestjs mailer. And from what I can tell the HandlebarsAdapter allows you to pass down helpers via the constructor. What if you make use of the forRootAsync and then we create a helper function on the I18nService that can be passed down directly? This way you don't have to copy any code and it's a bit easier to implement.

DennisSnijder commented 2 years ago

@toonvanstrijp That does help a lot! That's somewhat like the approach I'm using at the moment 😄. Not the easy plug and play version like you got going on with the viewEngine: 'hbs', but that would be a great addition!

My current module setup:

@Module({
  imports: [
    I18nModule.forRoot(...I18nOptions),

    MailerModule.forRootAsync({
      inject: [ I18nService ],
      useFactory: (i18nService: I18nService) => ({
        transport: {
          host: process.env.EMAIL_HOST,
          port: process.env.EMAIL_PORT,
          auth: {
            user: process.env.EMAIL_ID,
            pass: process.env.EMAIL_PASS
          }
        },
        template: {
          dir: path.join(__dirname, '../resources/templates/'),
          adapter: new HandlebarsAdapter(getHandlebarsHelpers(i18nService))
        },
      })
    }),
  ]
})

and the Handlebars helpers being: (pretty much copied this from this repo)

export const getHandlebarsHelpers = (i18nService: I18nService) => {
  return {
    t: (key: string, args: any, options: any) => {
      if (!options) {
        options = args;
      }

      const lang = options.lookupProperty(options.data.root, 'i18nLang');
      return i18nService.translate(key, {lang, args});
    }
  };
}

If there would be something like getHandlebarHelpers on the I18nService, that would be awesome 😄

toonvanstrijp commented 2 years ago

@DennisSnijder thanks! It's released in V9.0.8. I also added a doc page: https://nestjs-i18n.com/guides/mailer. @DennisSnijder thanks for the help!

DennisSnijder commented 2 years ago

@toonvanstrijp Woah, that was fast! Thanks! 🥳

tamert commented 2 years ago

Good solve but async problem to me

[object Promise]

aroncal commented 3 months ago

Hi,

In my tests sending an email with MailerService.sendMail() and using Handlebars, getHandlebarsHelpers isn't finding the lang from the i18nLang property, here:

const lang = options.lookupProperty(options.data.root, 'i18nLang');

I found out I have to pass i18nLangmanually in the context of the call to sendMail, like this:

@Controller('contact')
export class ContactController {

  constructor(
    private readonly mailerService: MailerService,
  ) {}

  @Post('')
  async sendEnquiry(
    @Body() data: EnquiryDto,
    @I18n() i18n: I18nContext
  ): Promise<Result<any>> {

      this.mailerService
        .sendMail({
          to: enquiry.contactData?.email,
          from: 'me@example.com',
          subject: `My subject`,
          template: 'enquiry',
          context: {
            i18nLang: i18n.lang || 'es-ES',
            enquiry,
          },
        })
        .then((res) => {
          resolve(res);
        })
        .catch((err) => {
          console.error(err);
          reject(err);
        });
  } 

}

I think this isn't documented anywhere.