Reference
This page covers the public @passlock/server helpers for mailbox challenges in more detail.
Shared request fields
Section titled “Shared request fields”All mailbox challenge helpers take shared Passlock config as their second argument:
tenancyId- your Passlock tenancy IDapiKey- your tenancy API keyendpoint- optional API base URL override, defaults tohttps://api.passlock.dev
Mailbox challenges share a few important concepts:
purposeis an application-defined string. Passlock validates it as a 1-64 character string using onlyA-Z,a-z,0-9,.,_,:, and-.metadatamust be JSON-compatible.challengeIdidentifies the challenge.secretis stored by your app and sent back during verification.codeis the one-time code you deliver to the user.
createMailboxChallenge
Section titled “createMailboxChallenge”Use createMailboxChallenge to start a flow and obtain the generated challengeId, secret, code, and rendered message content (html and text).
import { Passlock } from "@passlock/server";
const passlock = new Passlock({ tenancyId: "myTenancyId", apiKey: "myApiKey"});
const result = await passlock.createMailboxChallenge({ email: "jdoe@example.com", purpose: "signup", userId: "123", metadata: { signupId: "signup_123", }, invalidateOthers: true, skipRateLimit: true});
if (result.success) { console.log(result.value.challenge.challengeId); console.log(result.value.challenge.message.text);} else { console.error(result.error.message);}Important create options
Section titled “Important create options”invalidateOthersdeletes other pending challenges with the same purpose and userId or email.- When
userIdis present, Passlock scopes invalidation byuserId; otherwise it scopes byemail. skipRateLimitbypasses mailbox challenge rate limiting and is usually best left unset.
Create errors
Section titled “Create errors”@error/ChallengeRateLimited@error/Forbidden
getMailboxChallenge
Section titled “getMailboxChallenge”Use getMailboxChallenge to read a pending challenge without exposing the secret or code. This is useful when you need to restore an in-progress flow or show which mailbox is being verified.
const result = await passlock.getMailboxChallenge({ challengeId: "challenge_123",});
if (result.success) { console.log(result.value.email); console.log(result.value.metadata);} else { console.error(result.error.message);}Get errors
Section titled “Get errors”@error/NotFound@error/Forbidden
verifyMailboxChallenge
Section titled “verifyMailboxChallenge”Use verifyMailboxChallenge to check the code against the stored challenge. Pass challengeId, secret, and the user-supplied code.
const result = await passlock.verifyMailboxChallenge({ challengeId: "challenge_123", secret: "abc123def-ghi456jkl-mno789pqr", code: "123456",});
if (result.success) { console.log(result.value.challenge);} else { console.error(result.error.message);}Successful verification returns ChallengeVerified with a readable nested challenge. The response excludes the secret and one-time code, so your app should keep validating the returned purpose, email, and userId against local expectations.
Verify errors
Section titled “Verify errors”@error/InvalidChallenge@error/InvalidChallengeCode@error/ChallengeExpired@error/ChallengeAttemptsExceeded@error/Forbidden
deleteMailboxChallenge
Section titled “deleteMailboxChallenge”Use deleteMailboxChallenge when a user cancels a flow or when you want to clear pending challenge state explicitly.
const result = await passlock.deleteMailboxChallenge({ challengeId: "challenge_123",});
if (result.success) { console.log("challenge deleted"); // "ChallengeDeleted"} else { console.error(result.error.message);}deleteMailboxChallenge resolves to { _tag: "ChallengeDeleted" }.
Delete errors
Section titled “Delete errors”@error/Forbidden
If you are not using the JS/TS server library, the same behavior is documented in the REST API mailbox challenge reference.