Skip to content

Email verification

Use mailbox challenges during signup when you want to verify that the user owns the email address before creating or activating their account.

import { Passlock, isChallengeRateLimitedError } from "@passlock/server";
const passlock = new Passlock({ ... });
const result = await passlock.createMailboxChallenge({
email: "jdoe@example.com",
purpose: "signup",
metadata: {
signupId: "signup_123",
},
invalidateOthers: true,
});
if (result.success) {
// message contains rendered HTML and plain-text email content.
// result.challenge.code is also available if you prefer
// to render your own email.
const { challengeId, secret, message, email } = result.value.challenge;
// save this in the user's session or a secure HTTP only cookie
await savePendingChallenge({
challengeId,
secret,
email,
});
await emailCodeToUser({ email, message });
} else if (isChallengeRateLimitedError(result.error)) {
return showRateLimitMessage(result.error.retryAfterSeconds);
} else {
throw new Error(result.error.message);
}
Choose your code style
import { Passlock } from "@passlock/server";
const passlock = new Passlock({ ... });
// fetch from the user's session or secure cookie
const pending = await loadPendingChallenge();
const result = await passlock.verifyMailboxChallenge({
challengeId: pending.challengeId,
secret: pending.secret,
code: form.code,
});
if (result.success) {
const { challenge } = result.value;
if (challenge.purpose !== "signup") {
throw new Error("Unexpected challenge purpose");
}
if (challenge.email !== pending.email) {
throw new Error("Email mismatch");
}
await createUser({ email: challenge.email });
await markEmailVerified(challenge.email);
await clearPendingChallenge();
} else {
return showVerificationError();
}
Choose your code style

If you also support non-Node.js backends, the same flow is available through the REST API mailbox challenge endpoints.