Outcome
Store pending personal recovery email verification and request notification acceptance; it does not prove delivery or verification.
These examples prove local handler behavior with real isolated SQL and explicitly simulated identity/permission dependencies.
Before you start
-
Verify the intended authenticated identity and the current credential-scoped action before preparing input. Self and managed-user security operations have different authority; a session id does not authorize a different user.
-
Use authorized identity and user/session discovery. Recovery security data and session/device identifiers must remain inside the caller-approved workflow.
-
Review the validated plan and intended effect before confirmation. Logout can require reauthentication; recovery verification can send an email. Fixture examples authorize no live mutation or external delivery.
Discover the target
topolo whoami --json
topolo apps --json
topolo actions get app_topolo_auth.me.recovery_email.request_verification --detail --json
For user/session actions use the caller subject or a user returned by authorized users discovery, then sessions.list for that exact user. For recovery read me.recovery_email.get before reviewing an approved address. Tenant identity takes no override input.
Input fields
| Field | Required | Meaning |
|---|---|---|
email | Yes | Personal recovery address chosen and approved by the caller. The handler trims and lowercases it. Organization members must use an address different from their primary work email. A request writes pending verification state and can send an email; it does not verify ownership of the address. |
Effects
-
Writes/replaces pending recovery verification state, then requests an email notification. Storage and notification are not atomic.
-
Does not verify the recipient address. A prior verified row may be returned while the new pending row is hidden by selection order.
Interpret the response
Success is HTTP 200 with raw target success/message/data/timestamp. CLI/MCP can wrap or project that response. Record timestamps are epoch seconds; the envelope timestamp is ISO text. These local fixtures simulate token identities, so no response proves live sign-in or browser OAuth recovery.
Recovery read selects the verified personal_recovery row first, then verified/updated time; it is not a list of every pending address. recoveryEmailRequired is based on that selected row verifiedAt. requiresDistinctRecoveryEmail is based on the presence of identity organization memberships, not the currently selected context alone.
Recovery request stores a normalized pending address and token hash before attempting Notify. accepted and notificationStatus describe event ingestion, never email delivery or recipient verification. expiresAt describes the newly requested token, while personalRecoveryEmail can still be the previous verified address when a different pending email was stored. A repeated address request replaces its token hash/expiry, preserving existing verification state.
A Notify 500/502 can follow a successful pending-email SQL write. Read current recovery status before reviewing a retry, but that read can hide pending rows behind an older verified address. Confirm the requested mailbox through the actual verification journey and preserve the failure evidence; this action family has no pending-address list or cancellation action. An email globally attached to another identity is not reassigned and currently returns SERVER_ERROR.
Some environments can return a one-time verificationToken even when notification acceptance failed. Treat it as a secret and preserve protected transport handling; an echoed token is not delivery proof. Never copy a live token into documentation or an audit artifact. Fixture tokens are deliberately unusable. This family does not expose a verification-completion MCP action; the recipient uses the verification page and explicit confirmation.
Missing access tokens return AUTH_ERROR. A thrown invalid-token error currently becomes SERVER_ERROR in these handlers. Session/recovery/logout prefer an access cookie when present; tenant lookup prefers the Bearer header. Do not combine identities across cookie and Bearer transports or infer an OAuth expiry from every 401/500.
Worked examples
These are complete raw target-handler responses, not CLI/gateway envelopes or live authorization proof. Replace synthetic identifiers using authorized discovery. Save only the reviewed input as payload.json; inspect validation and plan before executing. --confirm assumes the intended operation is approved. Storage and response read-back require separate live token rejection, delivery and recipient verification proof where relevant. Cookies and Bearer tokens must identify the intended principal consistently.
Normalized recovery address is stored and notification is accepted, not delivered
Input · payload.json:
{
"email": "Recovery@Example.test"
}
CLI:
topolo actions validate app_topolo_auth.me.recovery_email.request_verification --data @payload.json --json
topolo actions plan app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
topolo actions call app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.me.recovery_email.request_verification",
"input": {
"email": "Recovery@Example.test"
},
"confirm": true
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Recovery email verification queued",
"data": {
"personalRecoveryEmail": {
"id": "uem_hZa-yhthJUYb",
"userId": "usr_self",
"email": "recovery@example.test",
"kind": "personal_recovery",
"organizationId": null,
"verifiedAt": null,
"verificationExpiresAt": 1790985600,
"loginEnabled": false,
"recoveryEnabled": false,
"createdAt": 1790899200,
"updatedAt": 1790899200
},
"accepted": true,
"notificationEventId": "event_fixture_accepted",
"notificationStatus": "accepted",
"expiresAt": 1790985600
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
Existing verified address is returned while a different pending address is stored
Input · payload.json:
{
"email": "new-recovery@example.test"
}
CLI:
topolo actions validate app_topolo_auth.me.recovery_email.request_verification --data @payload.json --json
topolo actions plan app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
topolo actions call app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.me.recovery_email.request_verification",
"input": {
"email": "new-recovery@example.test"
},
"confirm": true
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Recovery email verification queued",
"data": {
"personalRecoveryEmail": {
"id": "uem_hZa-yhthJUYb",
"userId": "usr_self",
"email": "recovery@example.test",
"kind": "personal_recovery",
"organizationId": null,
"verifiedAt": 1790899199,
"verificationExpiresAt": 1790985600,
"loginEnabled": true,
"recoveryEnabled": true,
"createdAt": 1790899200,
"updatedAt": 1790899200
},
"accepted": true,
"notificationEventId": "event_fixture_duplicate",
"notificationStatus": "duplicate",
"expiresAt": 1790985600
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
Environment-controlled token response does not claim delivery
Input · payload.json:
{
"email": "token-only@example.test"
}
CLI:
topolo actions validate app_topolo_auth.me.recovery_email.request_verification --data @payload.json --json
topolo actions plan app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
topolo actions call app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.me.recovery_email.request_verification",
"input": {
"email": "token-only@example.test"
},
"confirm": true
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Recovery email verification token issued without delivery",
"data": {
"personalRecoveryEmail": {
"id": "uem_hZa-yhthJUYb",
"userId": "usr_self",
"email": "recovery@example.test",
"kind": "personal_recovery",
"organizationId": null,
"verifiedAt": 1790899199,
"verificationExpiresAt": 1790985600,
"loginEnabled": true,
"recoveryEnabled": true,
"createdAt": 1790899200,
"updatedAt": 1790899200
},
"accepted": false,
"notificationEventId": null,
"notificationStatus": "failed",
"expiresAt": 1790985600,
"verificationToken": "unusable-fixture-recovery-token-5"
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
Personal identity may use its primary email
Input · payload.json:
{
"email": "personal@example.test"
}
CLI:
topolo actions validate app_topolo_auth.me.recovery_email.request_verification --data @payload.json --json
topolo actions plan app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
topolo actions call app_topolo_auth.me.recovery_email.request_verification --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.me.recovery_email.request_verification",
"input": {
"email": "personal@example.test"
},
"confirm": true
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Recovery email verification queued",
"data": {
"personalRecoveryEmail": {
"id": "uem_3nDdTRHUCCQW",
"userId": "usr_personal",
"email": "personal@example.test",
"kind": "personal_recovery",
"organizationId": null,
"verifiedAt": null,
"verificationExpiresAt": 1790985600,
"loginEnabled": false,
"recoveryEnabled": false,
"createdAt": 1790899200,
"updatedAt": 1790899200
},
"accepted": true,
"notificationEventId": "event_fixture_personal",
"notificationStatus": "processed",
"expiresAt": 1790985600
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
Verify the result
-
Inspect accepted, notificationEventId and notificationStatus as ingestion evidence only; verify actual email delivery separately through Notify delivery evidence.
-
Read recovery status, then complete the requested-address verification page as the recipient. Require the intended address and verifiedAt before claiming verification. A prior verified address does not prove the requested address was selected or verified.
Errors and recovery
Work email cannot also be the organization member recovery email
Captured local refusal · HTTP 400:
{
"input": {
"email": "work@example.test"
},
"response": {
"success": false,
"error": {
"message": "Use a personal recovery email that is different from your work email.",
"code": "VALIDATION_ERROR",
"details": {
"email": true
},
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Notify refusal occurs after the pending email SQL write
Captured local refusal · HTTP 502:
{
"input": {
"email": "pending-after-failure@example.test"
},
"response": {
"success": false,
"error": {
"message": "Isolated Notify rejection",
"code": "NOTIFY_EMIT_FAILED",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Unconfigured delivery returns server error after storage
Captured local refusal · HTTP 500:
{
"input": {
"email": "unconfigured@example.test"
},
"response": {
"success": false,
"error": {
"message": "Recovery email delivery is not configured on Topolo Auth.",
"code": "NOTIFY_NOT_CONFIGURED",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
An address attached to another identity is not reassigned
Captured local refusal · HTTP 500:
{
"input": {
"email": "personal@example.test"
},
"response": {
"success": false,
"error": {
"message": "Email is already attached to another Topolo account",
"code": "SERVER_ERROR",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Anonymous recovery write is refused
Captured local refusal · HTTP 401:
{
"input": {
"email": "anonymous@example.test"
},
"response": {
"success": false,
"error": {
"message": "Access token required",
"code": "AUTH_ERROR",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Missing identity cannot set recovery email
Captured local refusal · HTTP 404:
{
"input": {
"email": "missing@example.test"
},
"response": {
"success": false,
"error": {
"message": "User not found",
"code": "NOT_FOUND",
"details": null,
"timestamp": "2026-10-02T00:00:00.000Z"
}
}
}
Published recovery guidance
-
state_conflict(HTTP 409): Read the current resource state, reconcile the intended change, and retry only if still valid. -
VALIDATION_ERROR(HTTP 400): Correct the reported email field and select a caller-approved personal address distinct from the work email for organization members. -
AUTH_ERROR(HTTP 401): Verify the intended identity and access-token transport. Refresh only an expired credential for that same approved context; do not switch organizations automatically. -
NOT_FOUND(HTTP 404): Refresh authorized user/session discovery or recovery status. Session deletion can also map a storage error to NOT_FOUND; do not claim successful revocation from this response. -
SERVER_ERROR(HTTP 500): Inspect the original error and current state. Invalid-token exceptions can return 500. A recovery write or token-version bump may already have happened; do not blindly repeat a mutation or claim a delivery/revocation outcome. -
NOTIFY_NOT_CONFIGURED(HTTP 500): The pending email may already be stored. Preserve the delivery configuration failure and obtain authorized service repair before reviewing a new request; do not bypass Notify with an invented route. -
NOTIFY_EMIT_FAILED(HTTP 502): Preserve the actual Notify error code/issues. The email row may already be stored; inspect current recovery status and requested mailbox before a reviewed retry. Acceptance and delivery remain separate.
Recovery boundaries
No cancel/delete-pending or generic undo action is published in this family. Preserve the prior selected record and reviewed mailbox. A failure can follow storage; inspect current status and requested mailbox before a new confirmed request. Repeated requests replace the address token and may invalidate its previous link. Never publish a verification secret or invent a completion endpoint.
Discovery and evidence
This record belongs to app_topolo_auth. Local tests prove real canonical session/recovery email SQL, target-user authorization and handler responses with simulated tokens, identity lookup and Notify results. Verification state is seeded; tokens are unusable. They do not prove live JWT rejection, browser OAuth recovery, email delivery, recipient verification, deployment or production completion.