Solving the 'Silent Fail' in Ory Kratos Email Verification
Avoid registration failures in Ory Kratos by using a local SMTP mock to verify that email tokens are sent and identities are correctly marked as verified.
26 Aug 2026, 10:58 UTC

The Problem: The Registration Black Hole
When implementing self-service registration in Ory Kratos, a common failure point is the "silent drop." If your SMTP courier is misconfigured, Kratos may report a successful request to the user, but the verification email is never delivered. The user is left waiting for a link that will never arrive, resulting in abandoned sign-ups and a broken onboarding experience.
Thesis: Verifying the Pipeline with a Local Mock
To avoid production failures, you must decouple the Kratos logic from your production mail server during development. By using a local SMTP mock (like Maildev), you can intercept the verification token and confirm that the identity is correctly marked as verified in the Kratos database before moving to a live environment.
Configuration for Local Testing
Kratos manages registration flows via a YAML configuration. To enable email verification, you must enable the email method and define a courier. The following configuration assumes you are running a local SMTP mock on port 1025.
# kratos.yml
selfservice:
registration:
methods:
email:
enabled: true
courier:
smtp:
from: "no-reply@example.com"
uri: "smtp://localhost:1025"
identity:
traits:
- email: verified
Note: The identity.traits section ensures that the verified boolean is tracked as part of the user's identity profile.
Worked Example: Testing the Flow via API
Since Kratos flows are stateless and driven by IDs, you can test the entire sequence using curl from your terminal. These commands should be run against your Kratos public API endpoint (typically port 4433).
1. Initialize the Registration Flow
First, request a new registration flow to receive a unique Flow ID.
curl -s -X GET "http://localhost:4433/self-service/registration/methods" -H "Accept: application/json"
Extract the id from the response (e.g., abc123flow). This ID is short-lived and will expire by default in 10 minutes.
2. Submit the User Email
Submit the email address to trigger the courier to send the verification link.
curl -s -X POST "http://localhost:4433/self-service/registration/methods/email" \n -H "Content-Type: application/json" \n -d '{ "flow": "abc123flow", "email": "test-user@example.com" }'
3. Intercept and Activate
Check your SMTP mock (e.g., Maildev UI). You will find an email containing a link similar to http://localhost:4433/self-service/registration?token=.... Click this link in a browser. Kratos will validate the token, create the identity, and redirect you to the success page.
4. Confirm Identity Status
Verify that the identity was created with the verified trait set to true. This requires access to the Admin API (port 4434) and an admin token.
curl -s -X GET "http://localhost:4434/admin/identities?filter.email.value=test-user@example.com" \n -H "Authorization: Bearer $KRATOS_ADMIN_TOKEN"
Expected result: The JSON response should contain "traits":{"email":"test-user@example.com","verified":true}.
Limitations and Trade-offs
The primary trade-off in this architecture is the Flow Lifespan. By default, registration flows expire quickly. If a user has a slow email provider or delays clicking the link, they will encounter a 410 Gone error. While you can increase selfservice.registration.flow.lifespan, doing so increases the window for potential token replay attacks. It is recommended to keep this window tight and implement a "Resend Verification Email" UI pattern.
Actionable Summary
To ensure your registration flow is robust: deploy a local Kratos instance, point the courier to a mock SMTP server, and execute the four-step API sequence (Init $\rightarrow$ Submit $\rightarrow$ Activate $\rightarrow$ Confirm). Only once the verified: true trait is confirmed in the Admin API should you transition the courier.smtp.uri to your production mail provider.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.