Transactional emails in Meteor


#1

I’ve read the Meteor documentation and guide at least 5 times, looking for a way to handle the transactional emails myself using Accounts package - no luck.

It seems I have to provide a SMTP endpoint to Meteor, which sucks. Also, customizing the email templates in Accounts.emailTemplates is quite a rigid solution.

I’d like to use my mail providers API for sending, since it provides all kinds of benefits. That way, I could also use my already existing email templates with easy variable replacing.

Seems like it’s quite hard to do in Meteor.

All I could find is this gist:


#2

You don’t need to use the built-in email functions from meteor, if your mail provider has an API you can use that directly. As an example, many people are using sendgrid these days, there are excellent api’s using npm for it. Sure you could use the built-in Email.send(), but as you say, using your mail provider’s api allows for much more functionality.

You will need to create the accounts functionality yourself, like forgot password or verify email, but that really isn’t that hard. It’s a matter of creating a reset token when they click reset password, storing it in the user object, then sending an email with that token, making a route for password resets with the reset token as an id parameter and handling password change on that page. Same thing for verification and similar account stuff.

It’s a bit of extra work, but allows you to fully customize your emails and you can add/change that part of the account functionality as you like, you might want to implement more security steps for example (2step verification, security question, etc.)


#3

Sure thing, but I find it puzzling that nobody thought of adding a functionality to the Accounts package, that would just hand over the token / url needed. To me it seems like really low hanging fruit to improve the already really liked Accounts-package.

Now it forces you to

a) Use Meteor’s email with SMTP servers and clumsy templates etc. etc…
b) Implement all the nice things in Accounts from scratch


#4

For email templates, I’m using Blaze server-side rendering (meteorhacks:ssr package) and I’m very satisfied with this setup. Basically I have private/emails/ folder where I put all emails, and then have a file included by the server only:

var emailTemplates = {
  'emailLayout': 'layout.html',
  'welcome': 'welcome.html',
  'someOtherTemplate': 'someOtherTemplate.html'
}

// Compile HTML templates
Meteor.startup(function() {
  for (var template in emailTemplates) {
    SSR.compileTemplate(template, Assets.getText('emails/' + emailTemplates[template]) );
  }
});

The emailLayout template is the general email layout (header, footer), which gets included from the individual templates. To render the template with some data just before sending the email I just do:

var html = SSR.render(templateName, dataObject);

Then feed it as a html parameter to Email.send method.


#5

Those aren’t the only options. In one of our apps, we use Mandrill to send the verification emails with custom Handlebars templates

in another app we’ve implemented Auth0 instead of Meteor accounts. Now we can take our users with us if we leave Meteor–and we get more nice things like Sign In as User.


#6

Are you doing that with the Accounts-package somehow, or have you created your own flow?


#7

at first we came up with a funny hack. We didn’t know how to prevent Meteor from sending those verification emails, so we would “shoot them down”. All our emails go through Mandrill (transactional email service), so we set the Meteor verification emails subject to “rejectverificationemailmail” then made a rule in Mandrill to reject emails with that subject. So now these emails will never be sent.

Then we take the verification url and send it via Mandrill using a handlebars template

 var templateName = locale === 'zh' ? 'activate-email-zh' : 'wre-activate-email-en';
    Mandrill.messages.sendTemplate({
      template_name: templateName,
      template_content: [],
      message: {
        to: [
          {email: user.emails[0].address},
        ],
        global_merge_vars: [
          {
            name: 'activationUrl',
            content: url,
          }
        ]
      },
    });

Later our CTO discovered a better way to more directly prevent Meteor from sending those verification emails (using some unofficial Meteor api function I think)


#8

This is the whole point.

If you later came up with a clean way to achieve this, I’m all ears :slight_smile:


#9

I feel like our approach saved us from having to write “all the nice things about accounts from scratch.” We just blocked the native verification email so we can send our own html version. That’s it. We didn’t need to write our own endpoint for the verification link or anything like that.

We (well, my CTO to be honest) looked at the underlying code in Meteor for accounts and we use his modified version instead, but still leverage a lot of things about how account verification works.

//our code
const _verifyEmailToken = ({token})=> {
  var user = Meteor.users.findOne(
    {'services.email.verificationTokens.token': token});
  if (!user) {
    throw new Meteor.Error(403, "Verify email link expired");
  }

  var tokenRecord = _.find(user.services.email.verificationTokens,
    function(t) {
      return t.token == token;
    });
  if (!tokenRecord) {
    return {
      userId: user._id,
      error: new Meteor.Error(403, "Verify email link expired")
    };
  }

  var emailsRecord = _.find(user.emails, function(e) {
    return e.address == tokenRecord.address;
  });
  if (!emailsRecord) {
    return {
      userId: user._id,
      error: new Meteor.Error(403, "Verify email link is for unknown address")
    };
  }

  try {
    Promise.await(updateAuth0UserEmail({meteorUserId: user._id, email: tokenRecord.address, isVerified: true}));
  } catch (err){
    return {
      userId: user._id,
      error: new Meteor.Error(400, "Unable to call email updater service.")
    };
  }
...

Based on

Hope that helps someone!