nest-modules / mailer

📨 A mailer module for Nest framework (node.js)
https://nest-modules.github.io/mailer/
MIT License
847 stars 177 forks source link

Dynamic Transporters Fails to Connect #1235

Closed ogheneovo12 closed 2 hours ago

ogheneovo12 commented 2 hours ago

Describe the bug On module initialization, i set custom transporters to the transporters map. Using the transporterName later in sendMail, leads to connection Error.

see initialisation code below

async onModuleInit() {
    //register all transporters
    const workspaceSMTPS = await this.workspaceSMTPConfigModel
      .find({})
      .populate('workspace');
    workspaceSMTPS.forEach((doc) =>
      this.mailerService.addTransporter(
        doc.workspace?._id?.toString(),
        this.getSMTPConfig(doc),
      ),
    );
    this.logger.log('SMTPS TRANSPORTERS LOADED SUCCESSFULLY');
  }

The getSMTPConfig basically just decrypts auth credentials; I have verified that it returns actual configuration, which you will see reflected in sendMail.

 async sendMemberResetPasswordLink(
    member: WorkspaceMemberDocument,
    token: Token,
  ) {
    //checks if smtp has been configured and returns the name, if not undefined 
    const transporterName = this.getWorkspaceTransporterName(member.workspace);

    // const options = await this.mailerService['transporters']?.get(transporterName).options;
    // this.mailerService.addTransporter(transporterName, options);

    try {
      await this.mailerService.sendMail({
        to: member.email,
        template: './custom-base',
        subject: `${member?.workspace?.name} Password Reset`,
        context: {
          url: `https://${member?.workspace?.custom_domain_url || member?.workspace?.workspace_url}/reset-member-password?otp=${token.token}`,
          name: `${member?.first_name} ${member?.last_name}`,
          content: 'member_password_reset',
          title: `${member?.workspace?.name} Password Reset`,
          workspace_name: member.workspace.name,
          workspace_logo: member.workspace?.organization.logo?.url,
          workspace_support_email: `${member?.workspace?.organization?.email}`,
        },
        transporterName,
      });
    } catch (err) {
      console.log(err);
    }
  }

with the above i get the below error

  Error: connect ECONNREFUSED 127.0.0.1:587
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1607:16)
    at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  errno: -61,
  code: 'ESOCKET',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 587,
  command: 'CONN'
}

Now uncommenting the below portion of the above code, i.e., re-registering the transporter again from the options existing in the transport map, seems to work.

    // const options = await this.mailerService['transporters']?.get(transporterName).options;
    // this.mailerService.addTransporter(transporterName, options);

Now I am tempted to keep a map of transporters myself and add a transporter for every sendMail call, but that's not efficient.

Basically, the custom transporter only works if the transporter was added immediately after the sendMail is called.

The pre-registered transporters stored in memory should always work with the correct configurations.

ogheneovo12 commented 2 hours ago

Found the bug; i feel so foolish ATM

I had applied async to this.getSMTPConfig(doc) when it shouldn't be an async method, i didn't know why i had also assumed NodeMailer stored options as promise getter, even though i found it off initially,

const options = await this.mailerService['transporters']?.get(transporterName).options;

Apparently i was the one setting a promissive option

  this.mailerService.addTransporter(
        doc.workspace?._id?.toString(),
        this.getSMTPConfig(doc), //Asynchronous
      )