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.
Start the signup challenge
Section titled “Start the signup challenge”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);}import { Passlock, isChallengeRateLimitedError } from "@passlock/server/unsafe";
const passlock = new Passlock({ ... });
try { const result = await passlock.createMailboxChallenge({ email: "jdoe@example.com", purpose: "signup", metadata: { signupId: "signup_123", }, invalidateOthers: true, });
// 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.challenge;
// save this in the user's session or a secure HTTP only cookie await savePendingChallenge({ challengeId, secret, email, });
await emailCodeToUser({ email, message });} catch (error) { if (isChallengeRateLimitedError(error)) { return showRateLimitMessage(error.retryAfterSeconds); } else { throw error; }}import { createMailboxChallenge, isChallengeRateLimitedError,} from "@passlock/server";
const result = await createMailboxChallenge({ email: "jdoe@example.com", purpose: "signup", metadata: { signupId: "signup_123", }, invalidateOthers: true,}, { tenancyId, apiKey });
if (result.success) { // message contains rendered HTML and plain-text email content. // result.value.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);}import { createMailboxChallenge } from "@passlock/server/unsafe";
try { const result = await createMailboxChallenge({ email: "jdoe@example.com", purpose: "signup", metadata: { signupId: "signup_123", }, invalidateOthers: true, }, { tenancyId, apiKey });
// 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.challenge;
// save this in the user's session or a secure HTTP only cookie await savePendingChallenge({ challengeId, secret, email, });
await emailCodeToUser({ email, message });} catch (error) { if (isChallengeRateLimitedError(error)) { return showRateLimitMessage(error.retryAfterSeconds); } else { throw new Error(error); }}
Choose your code style
Verify the submitted code
Section titled “Verify the submitted code”import { Passlock } from "@passlock/server";
const passlock = new Passlock({ ... });
// fetch from the user's session or secure cookieconst 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();}import { Passlock } from "@passlock/server/unsafe";
const passlock = new Passlock({ ... });
// fetch from the user's session or secure cookieconst pending = await loadPendingChallenge();
try { const verified = await passlock.verifyMailboxChallenge({ challengeId: pending.challengeId, secret: pending.secret, code: form.code, });
if (verified.challenge.purpose !== "signup") { throw new Error("Unexpected challenge purpose"); }
if (verified.challenge.email !== pending.email) { throw new Error("Email mismatch"); }
await createUser({ email: verified.challenge.email, });
await markEmailVerified(verified.challenge.email); await clearPendingChallenge();} catch (error) { return showVerificationError();}import { verifyMailboxChallenge } from "@passlock/server";
// fetch from the user's session or secure cookieconst pending = await loadPendingChallenge();
const result = await verifyMailboxChallenge({ challengeId: pending.challengeId, secret: pending.secret, code: form.code,}, { tenancyId: "myTenancyId", apiKey: "myApiKey" });
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();}import { verifyMailboxChallenge } from "@passlock/server/unsafe";
// fetch from the user's session or secure cookieconst pending = await loadPendingChallenge();
try { const verified = await verifyMailboxChallenge({ challengeId: pending.challengeId, secret: pending.secret, code: form.code, }, { tenancyId: "myTenancyId", apiKey: "myApiKey" });
if (verified.challenge.purpose !== "signup") { throw new Error("Unexpected challenge purpose"); }
if (verified.challenge.email !== pending.email) { throw new Error("Email mismatch"); }
await createUser({ email: verified.challenge.email, });
await markEmailVerified(verified.challenge.email); await clearPendingChallenge();} catch (error) { return showVerificationError(error);}
Choose your code style
If you also support non-Node.js backends, the same flow is available through the REST API mailbox challenge endpoints.