5

i am kinda new to Loopback and Typescript so I have no idea how to implement this. I am trying to call Nodemailer directly, but so far I keep getting an error.

My Mailer service:

import { SentMessageInfo } from 'nodemailer';
import Mail = require('nodemailer/lib/mailer');
const nodemailer = require("nodemailer");

export class MailerService {
  async sendMail(mailOptions: Mail.Options): Promise<SentMessageInfo> {
    const transporter = nodemailer.createTransport({
      host: 'smtp.ethereal.email',
      port: 587,
      auth: {
        user: 'albert.grimes@ethereal.email',
        pass: 'qN85JT6SneBA9S5dhy'
      }
    });
    return await transporter.sendMail(mailOptions);
  }
}

My Mailer controller:

import { Request, RestBindings, get, ResponseObject } from 

'@loopback/rest';
import { inject } from '@loopback/context';
import { MailerService } from "../services";

export class MailController {
  constructor(
    @inject ???
    public mailerService: MailerService
  ) { }

  @get('/mail/acceptation')
  async sendEmail(email: string): Promise<any> {
    let info = await this.mailerService.sendMail({
      to: `${email}`,
      subject: 'testmail',
      html: '<p>Hallo</p>'
    })
    return info;
  }
}

I keep getting this as a error:

Unhandled error in GET /mail/acceptation: 500 Error: Cannot resolve injected arguments for MailController.prototype.sendEmail[0]: The arguments[0] is not decorated for dependency injection, but a value is not supplied

So what I am gathering from this is that I should inject a value in my controller, but I have no idea what.

Manish Balodia
  • 1,863
  • 2
  • 23
  • 37
djamaile
  • 695
  • 3
  • 12
  • 30

3 Answers3

5

email.service.ts

import Utils from '../utils';
import * as nodemailer from 'nodemailer';
import { IEmail } from '../type-schema';

export interface EmailManager<T = Object> {
  sendMail(mailObj: IEmail): Promise<T>;
}

export class EmailService {
  constructor() { }

  async sendMail(mailObj: IEmail): Promise<object> {
    const configOption = Utils.getSiteOptions();

    let transporter = nodemailer.createTransport(configOption.email);

    return await transporter.sendMail(mailObj);
  }
}

define your smtp option in your config file like following:-

"email": {
    "type": "smtp",
    "host": "smtp.gmail.com",
    "secure": true,
    "port": 465,
    "tls": {
      "rejectUnauthorized": false
    },
    "auth": {
      "user": "example@gmail.com",
      "pass": "sample-password"
    }
  }

in controller send mail like following way:-

import { EmailManager } from '../services/email.service';
import { EmailManagerBindings } from '../keys';

// inject in constructor
@inject(EmailManagerBindings.SEND_MAIL) public emailManager: EmailManager,

// call service method like following way
const mailOptions = {
          from: configOption.fromMail,
          to: getUser.email,
          subject: template.subject,
          html: Utils.filterEmailContent(template.message, msgOpt)
        };

        await this.emailManager.sendMail(mailOptions).then(function (res: any) {
          return { message: `Successfully sent reset mail to ${getUser.email}` };
        }).catch(function (err: any) {
          throw new HttpErrors.UnprocessableEntity(`Error in sending E-mail to ${getUser.email}`);
        });

Simple Way:- If you don't want to make a service function just import nodemailer in your controller and send mail, but its not a good approach.

import * as nodemailer from 'nodemailer';

let transporter = nodemailer.createTransport({
    "type": "smtp",
    "host": "smtp.gmail.com",
    "secure": true,
    "port": 465,
    "tls": {
      "rejectUnauthorized": false
    },
    "auth": {
      "user": "example@gmail.com",
      "pass": "sample-password"
    }
  });

 return await transporter.sendMail({
          from: "sender-email",
          to: "receiver-email",
          subject: "email-subject",
          html: "message body"
        });

Update:-

keys.ts

import { BindingKey } from '@loopback/context';    
import { EmailManager } from './services/email.service';    
import { Member } from './models';
import { Credentials } from './type-schema';

export namespace PasswordHasherBindings {
  export const PASSWORD_HASHER = BindingKey.create<PasswordHasher>('services.hasher');
  export const ROUNDS = BindingKey.create<number>('services.hasher.round');
}

export namespace UserServiceBindings {
  export const USER_SERVICE = BindingKey.create<UserService<Member, Credentials>>('services.user.service');
}

export namespace TokenManagerBindings {
  export const TOKEN_HANDLER = BindingKey.create<TokenManager>('services.token.handler');
}

export namespace EmailManagerBindings {
  export const SEND_MAIL = BindingKey.create<EmailManager>('services.email.send');
}

aplication.ts

import { BootMixin } from '@loopback/boot';
import { ApplicationConfig } from '@loopback/core';
import { RepositoryMixin } from '@loopback/repository';
import { RestApplication } from '@loopback/rest';
import { ServiceMixin } from '@loopback/service-proxy';
import * as path from 'path';
import { MySequence } from './sequence';

import { TokenServiceBindings, UserServiceBindings, TokenServiceConstants, } from './keys';
import { JWTService, TokenGenerator } from './services/jwt-service';
import { EmailService } from './services/email.service';
import { MyUserService } from './services/user-service';
import { AuthenticationComponent, registerAuthenticationStrategy, } from '@loopback/authentication';
import { PasswordHasherBindings, TokenManagerBindings, EmailManagerBindings } from './keys';
import { BcryptHasher } from './services/hash.password.bcryptjs';
import { JWTAuthenticationStrategy } from './authentication-strategies/jwt-strategy';

export class AmpleServerApplication extends BootMixin(ServiceMixin(RepositoryMixin(RestApplication))) {
  constructor(options: ApplicationConfig = {}) {
    super(options);

    this.setUpBindings();

    // Bind authentication component related elements
    this.component(AuthenticationComponent);

    registerAuthenticationStrategy(this, JWTAuthenticationStrategy);

    // Set up the custom sequence
    this.sequence(MySequence);

    // Set up default home page
    this.static('/', path.join(__dirname, '../public'));

    this.projectRoot = __dirname;

    this.bootOptions = {
      controllers: {
        dirs: ['controllers'],
        extensions: ['.controller.js'],
        nested: true,
      },
    };
  }

  setUpBindings(): void {
    this.bind(TokenServiceBindings.TOKEN_SECRET).to(TokenServiceConstants.TOKEN_SECRET_VALUE);
    this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to(TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE);

    this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);

    this.bind(EmailManagerBindings.SEND_MAIL).toClass(EmailService);
  }
}
Manish Balodia
  • 1,863
  • 2
  • 23
  • 37
  • late response, sorry. Could you show me how to bind your service to the application? That is the last thing I am having trouble with. – djamaile Jul 31 '19 at 08:12
  • @Peterrosevelt I have update my answer to show "how to bind Email service in my user controller file?" – Manish Balodia Jul 31 '19 at 11:54
0

You should have a file where your Bindings are stored. There you have to create the binding key:

export namespace MailerBindings {
  export const SERVICE = BindingKey.create<MailerService>('mailer.services');
}

To actually bind the service, you have to call the bind function in the constructor of your application:

this.bind(MailerBindings.SERVICE).toClass(MailerService);

Now you are able to inject the service using your binding:

@inject(MailerBindings.SERVICE) public mailerService: MailerService,
  • I am now even more confused. I don't understand why sending an email has to be so complex. Thanks though. – djamaile Jul 24 '19 at 13:03
  • It seems to be complex because of the implementation as a service. You could use it just as listed in the link below, but you would have to implement this for every part of your application where you want to send mails. By implementing it as a service you are able to use it in an easier way at multiple parts of your application. Link: https://nodemailer.com/about/ –  Jul 24 '19 at 14:13
0

There is a tutorial for this on https://loopback.io/doc/en/lb3/Email-connector.html. It has a special mail connector that can be used as a datasource. A model should be able to handle the sending details. The procedure should be pretty much the same as when you are creating a db connector using lb4 cli.

cli

lb4 datasource //select email

datasources.json

{
  ...
  "myEmailDataSource": {
    "connector": "mail",
    "transports": [{
      "type": "smtp",
      "host": "smtp.private.com",
      "secure": false,
      "port": 587,
      "tls": {
        "rejectUnauthorized": false
      },
      "auth": {
        "user": "me@private.com",
        "pass": "password"
      }
    }]
  }
  ...
}

model

module.exports = function(MyModel) {
  // send an email
  MyModel.sendEmail = function(cb) {
    MyModel.app.models.Email.send({
      to: 'foo@bar.com',
      from: 'you@gmail.com',
      subject: 'my subject',
      text: 'my text',
      html: 'my <em>html</em>'
    }, function(err, mail) {
      console.log('email sent!');
     cb(err);
    });
  }
};`
diego92sigma6
  • 354
  • 1
  • 3
  • 11