Send verification email with host address/port in link different from email serv address/port

788 views
Skip to first unread message

Akram Shehadi

unread,
Nov 18, 2015, 12:21:35 AM11/18/15
to StrongLoop
When a verification email is sent, a link is added that allows the new user to click on and complete the registration.

The link points to a host address and port, which is where the Strongloop API is hosted on. My issue is that I would like to have the clickable link point to a host address that is different than the email server address that is sending the verification email.


For example, let's say that my email server is Mailgun (I know they have API and SMTP options, so let's assume we go with SMTP to illustrate my point), and my Strongloop app is hosted by Heroku.

So when configuring the verification call, if I set the options host and port to Mailgun's address, the email gets sent fine but the link will also point to Malgun's host and address instead of Heroku's, and obviously this will not work.

On the other hand, if I put the Heroku host and port in the configuration, the email will never get sent because it will try to use Heroku as the email server.

Since the email verification procedure is a prototype defined in node_modules/loopback/common/models/user.js, I can't modify this code as every deploy in Heroku re-installs Strongloop every time (not sure if this can be avoided or not, but for now I'm not aware I can). 

Also, since the token is generated inside the prototype's verify() method, I'm guessing I can't easily extract the email verification process, unless I do the token generation outside (and also persist to DB, build the link, etc), since the token is generated as part of the link generation code.

I.e. in node_modules/loopback/common/models/user.js

User.prototype.verify = function(options, fn) {
    ...
    options
.host = options.host || (app && app.get('host'));
    options
.port = options.port || (app && app.get('port'));

    options
.restApiRoot = options.restApiRoot || (app && app.get('restApiRoot')) || '/api';
    options
.verifyHref = options.verifyHref ||
    
    options.protocol +
      '://' +
      options
.host +
     
':' +
      options
.port +

      options
.restApiRoot +
      userModel
.http.path +
      userModel
.sharedClass.find('confirm', true).http.path +
     
'?uid=' +
      options
.user.id +
     
'&redirect=' +
  
        options.redirect;
  // Email model
  var Email = options.mailer || this.constructor.email || registry.getModelByType(loopback.Email);
    
    var tokenGenerator = options.generateVerificationToken || User.generateVerificationToken;
    
    tokenGenerator(user, function(err, token) {
      if (err) { return fn(err); }

      user.verificationToken = token;
      user.save(function(err) {
        if (err) {
          fn(err);
        } else {
          sendEmail(user); //sendEmail() is defined inside verify(), and the options object passed to Email is the same, so the same host and port are used
        }
  });


In my overriding method (inside server/boot/0-model-override.js) I have:

User.afterRemote('create', function(context, user, next) {

    var options = {
      type
: 'email',
      to
: user.email,
     
from: myEmailAddress,
      subject
: 'My subject',
     
template: path.resolve(__dirname, '../views/verify.ejs'),
      redirect
: '/verified',
      user
: user,
      protocol
: 'https',
      host
: hostAddress,
      port
: portNumber
   
};

    user
.verify(options, function(err, response) {
     
if (err) {
       
next(err);
       
return;
     
}
      console
.log("Account verification email sent to " + options.to);
     
next();
     
return;
   
});
 
});


And so the host and port in options is used for both link and email server address.

I have also tried to use the "mailer" field 

i.e. adding 
      mailer: Email,

to the options, where Email is already correctly configured via the datasources.json file and model-config.json.

Also, I'm getting the email object as such:

var Email = app.models.Email;
 

So is there a way to accomplish this via configuration objects?

Or is there no other way than overriding the complete method, so I have to manually build the link, generate the token, etc, just so that I can use two different hoist addresses?

I would have guessed that people already using Strongloop hosted by Heroku, would want to send email from elsewhere and so this is probably a solved problem, but I just can't see how to easily do it.


Any pointers are greatly appreciated!
AkramEnter code here...






Akram Shehadi

unread,
Nov 18, 2015, 2:02:19 AM11/18/15
to StrongLoop
I guess it was not as confusing as I thought at first, or maybe I read something wrong, because it turns out that I can just pass the verifyHref linkpre-built, and the token gerneraion occurs after that.

Basically my issue was a misunderstanding apparently.

For the curious, the solution was to set the desired values for hostAddress, portNumber, restApiRoot and verifyRedirect, and then build the verification link and passing in the corresponding field:

var verifyLink = 'https://' +
                        hostAddress
+
                       
':' +
                        portNumber
+
                        restApiRoot
+
                       
'/Users/confirm' +
                       
'?uid=' +
                        user
.id +
                       
'&redirect=' +
                        verifyRedirect
;



var options = {
      type
: 'email',

      mailer
: Email,
      to
: user.email,
     
from: senderAddress,
      subject
: 'My Subject',

     
template: path.resolve(__dirname, '../views/verify.ejs'),
      redirect
: '/verified',
      user
: user,

      verifyHref
: verifyLink,
      host
: emailServerAddress,
      port
: emailServerPort
   
};

charles kakai

unread,
Jul 14, 2019, 12:23:00 AM7/14/19
to StrongLoop
Hey Akram,
I have deployed my api on heroku. I can't figure out how to set the port number because it changes on every deploy. Any assistance would be appreciated.

Akram Shehadi

unread,
Jul 16, 2019, 10:27:33 PM7/16/19
to StrongLoop
It's been some time since I last worked on this project so I don't exactly remember, but I believe Heroku sets the port variable, so you should be able to do something like
app.set('port', process.env.PORT || 3000);


Reply all
Reply to author
Forward
0 new messages