Accounts Password - Forgot Password

Is there a way to intervene and get the token and send the link through my own email function?

I’d like to send the forgot password e-mails through mailguns API so I can use their templates.

You could set your MAIL_URL setting to mailgun, so all your email goes via them

Hmm , But I don’t think there is a way I can utilize their templates then. I currently send e-mails through the API as such, So I was hoping to have a way to specify the link in the JSON data.

    'mail.newContact.notification'(contact){
        let sendTo = Meteor.settings.NOTIFY_EMAIL
        let subject = `New Contact for ${Meteor.settings.SITE_NAME}: ${contact.name}`
        const mg = mailgun({apiKey: Meteor.settings.MAILGUN_API, domain: Meteor.settings.MAILGUN_DOMAIN});
        let data = {
            from: Meteor.settings.SERVER_EMAIL,
            to: sendTo,
            subject: subject,
            template: "newcontact-notification",
            'h:X-Mailgun-Variables': `${JSON.stringify(contact)}`
        };
        mg.messages().send(data, function (error, body) {
            console.log(body);
        });
    },

In that case, you could replace the email package with your own implementation which uses the API instead.

Copy the email folder from the meteor repo into your project under /packages/email/ and then edit the code to use the API when Email.send is used

1 Like

That’s what I did, except I didnt copy the email package first, I just wrote my own Email.send() function which replaced the default function, and which uses the mailgun api.

1 Like

I’ve done this - a slightly different way to the above but perhaps overriding the Email.send might be a better approach? Not sure so I’ll post my solution in the hopes of a discussion.

My ‘Reset Password’ button, rather than using the Accounts Password built in helper function, calls my own method:

// Allow reset password email to be sent via Postmark
export const resetPassword = new ValidatedMethod({
	name: 'reset-password',
	validate: new SimpleSchema({
		email: String,
	}).validator(),
	run({ email }) {
    if ( !Meteor.isSimulation && Meteor.isServer ) {
      // This is a function that links to Postmark and sends out the reset password email
			const {
				sendResetPwEmail,
			} = require('../../helpers/server/emailNotifications');
			const user = Accounts.findUserByEmail(email);
			if (!user) {
				// TODO: Send an email here rather than throwing an error
				throw new Meteor.Error(
					'no-such-user',
					`No user exists with this username`,
				);
			}
			// Create a password reset token
			// Reference 1: https://github.com/meteor/meteor/blob/49c9d07be833561a789f57581416638bca4d249d/packages/accounts-password/password_server.js#L541-L554
			// Reference 2: https://github.com/poetic/accounts-passwordless/blob/master/server/send-login-email.js
			const tokenRecord = {
				token: Random.secret(),
				email,
				when: new Date(),
			};

			Meteor.users.update(
				{ _id: user._id },
				{ $set: { 'services.password.reset': tokenRecord } },
			);

			Meteor._ensure(user, 'services', 'password').reset = tokenRecord;

			// Send the email
			const resetUrl = Meteor.absoluteUrl(
				`reset-password/${tokenRecord.token}`,
			);
			sendResetPwEmail({
				to: user.emails[0].address,
				vars: {
          resetUrl,
					// other data from the user object to personalise this
				},
			});
		}
	},
});

As per the comments, I relied heavily on these two references to write this function:

  1. https://github.com/poetic/accounts-passwordless/blob/master/server/send-login-email.js
  2. https://github.com/meteor/meteor/blob/49c9d07be833561a789f57581416638bca4d249d/packages/accounts-password/password_server.js#L541-L554
1 Like

In addition to overwriting the default Email.send() function, I should add that I also replace the Accounts.emailTemplates... functions with functions which return a mailgun template object instead of text.

I use one ‘generic’ template and change the title, message and button by using template variables. E.g.:

Accounts.emailTemplates.resetPassword.text = function (user, url) {
	return {
		title: "Forgot your password?",
		html: "It happens to the best of us. The good news is you can change it right now.",
		link: url,
		button: "RESET YOUR PASSWORD",
	};
};

Then, replace Email.send with…

const mailgun = require('mailgun-js')({
	apiKey: Meteor.settings.private.mailgun.apiKey,
	domain: Meteor.settings.private.mailgun.domain,
});

// overwrite default Email.send function which is used internally by the Accounts packages...
Email.send = function(options) {
  ...
  // use mailgun templates?
  if (typeof options.text=='object') {
    options.template = 'generic';
    options['h:X-Mailgun-Variables'] = JSON.stringify(options.text);
    delete options.text;
  }
  ...
  mailgun.messages().send(options).then(...).catch(...);
  ...
}
1 Like