How to fire post-verify server-side actions?

If we need to fire a method when a user is created, we can hook into the Accounts.onCreateUser method.

However, if we need to fire a method when a user verifies their email, we can …? Currently I can’t find any server-side methods in the Meteor docs related to verifying an email address.

How do others go about running a method after a user verifies their email? Currently, I run an Apollo mutation via the client, but would prefer to hook into a Meteor accounts method already being run on the server.

Thanks for your insights!

Because email verification just uses a special login call under the hood, you should be able to hook into it using Accounts.validateLoginAttempt:

Accounts.validateLoginAttempt(function (attempt) {
  if (attempt.methodName !== 'verifyEmail') return true;
  // Extra logic for after successful email verification

  return true; // Allow login to complete
});

Assuming you’re not trying to prevent login here as verification has already completed before this function is called. If you wanted to affect verification status, you’d need to update the user document here

Basically, I don’t think you can do this the “right way”, which is why I’m suggesting you take advantage of another feature that does expose hooks and happens to be called at the exact time you want.

1 Like

What about Accounts.onEnrollmentLink() or Accounts-onEmailVerificationLink()? Edit: sorry, these are client only, but you could use them to fire a Method…

Personally, I’ve customized the verification and enrolment urls using:

Accounts.urls.verifyEmail = (token) => {
	return Meteor.absoluteUrl(`verify-email/${token}`);
};
Accounts.urls.resetPassword = (token) ...
Accounts.urls.enrollAccount = (token) ...

And then put my own logic on those routes as per: https://guide.meteor.com/accounts.html#email-flows

1 Like

Oh that’s much nicer.

I wonder if you could add middleware to the verify-email route on the server, do your own thing and then pass on the request as normal?

I’ve just dug out my own code of how I do it. I use my own client logic on the verify-email route to call Accounts.verifyEmail(), and in the success callback of that I call a server method to do what I need to do.

Here's an example from my own code template for this route (Vue.js):
<template lang="pug">
div.center
	h4 {{error || 'Verifying your email...'}}
	spinner.big(v-if="!error")
</template>

<script>
export default {
	data() { return {
		error: false,
	}},
	mounted() {
		Accounts.verifyEmail(this.$route.params.token, (error) => {
			if (error) {
				this.error = error.reason;
			} else {
				// send google conversion signal
				GAnalytics.event("account","email-verified");
				// set up the user's new company
				this.error = 'Email verified, setting up your company...';
				Meteor.call('server.companySettings.create', (err, res) => this.$router.push('/app'));
			}
		});
	},
}
</script>

@wildhart, thanks for sharing that snippet! That’s very similar to what I’m doing now in React:

const VerifyEmail = (props) => {
  const [verifyError, setVerifyError] = useState(null);
  const { match, history, userId, client } = props;
  const { mutate } = client;

  useEffect(() => {
    Accounts.verifyEmail(match.params.token, (error) => {
      if (error) {
        console.warn('VerifyEmail error');
        console.warn(error);
        Bert.alert(error.reason, 'danger');
        setVerifyError(`${error.reason}. Please try again.`);
      } else {
        setTimeout(() => {
          if (userId) {
            mutate({
              mutation: SEND_VERIFICATION,
              variables: { userId },
            });
          }
          Bert.alert('All set, thanks!', 'success');
          history.push('/');
        }, 1500);
      }
    });
  }, [verifyError]);

  return (
    <div className="VerifyEmail">
      <Alert bsStyle={!verifyError ? 'info' : 'danger'}>
        {!verifyError ? 'Verifying...' : verifyError}
      </Alert>
    </div>
  );
};

However, calling SEND_NOTIFICATION is pretty spotty, which is why I started looking for a more clean server-side option. Surprised there isn’t an explicit one, but thanks @coagmano, will have to research Accounts.validateLoginAttempt and see if that does the trick.

Can you elaborate on that - why is it ‘spotty’? :wink: Is the fixed 1500ms delay is sometimes not long enough for the new user to be logged in? If so, there’s ways around that… (e.g, creating a new Accounts.onLogin callback instead of using setTimeout).

A server-side solution would be interesting, let us know what you come up with!

1 Like