Implementing Ory Kratos Self‑Service Registration with Email Verification
Learn how to set up Ory Kratos self‑service registration with email verification using declarative flows, a Go SDK, and a mailer. Verify the flow end‑to‑end and understand key trade‑offs.
10 Feb 2026, 20:30 UTC

Problem
When building a micro‑service that needs to onboard users, developers often write custom registration APIs that handle email verification, password hashing, and account activation. This custom code is error‑prone, hard to audit, and duplicates functionality that many identity providers already offer. The question becomes: can we rely on a proven identity system to expose a self‑service registration flow that includes email verification, without writing backend logic?
Thesis
Ory Kratos’ declarative flow system lets you define a registration flow that automatically sends a signed verification link, validates it, and marks the account as verified—all without custom code. By wiring the flow JSON schema, a mailer, and a Go SDK, you can ship a secure, auditable registration experience in minutes.
Declarative Flow Setup
Kratos stores flows in PostgreSQL tables such as kratos_flow_registration. A registration flow is described by a JSON schema that lists the required steps. The following minimal schema defines a single step that requires email verification:
{
"id": "registration",
"steps": [
{
"id": "verification",
"type": "email_verification",
"config": {
"subject": "Confirm your email",
"template": "email_verify.html"
}
}
]
}
Upload this schema to Kratos via the POST /schemas endpoint or place it in the ./schemas directory before starting the server. The verification step triggers the mailer to send a link containing a signed JWT token.
Email Verification Mechanics
Kratos uses the mailer configuration to send emails. In config.yaml you must provide an SMTP server or a third‑party mailer API. Example SMTP setup:
mailer:
smtp:
host: smtp.example.com
port: 587
username: [contact removed]
password: REPLACE_WITH_SECRET
from: [contact removed]
tls: true
The verification link generated by Kratos looks like:
https://YOUR_DOMAIN/self-service/verification/api?flow=FLOW_ID&token=TOKEN
When the user clicks the link, Kratos validates the token against its signing key, marks the flow as completed, and stores the verified email in the user profile.
Integrating with a Go Service
Kratos exposes a Go SDK that simplifies flow initiation and status checks. Below is a concise example that initiates a registration flow and prints the verification link to the console. Replace placeholders with your environment values.
package main
import (
"context"
"fmt"
"github.com/ory/kratos-client-go"
"github.com/ory/kratos-client-go/api"
)
func main() {
cfg := kratosclient.NewConfiguration()
cfg.Host = "YOUR_KRATOS_HOST"
cfg.Scheme = "https"
client := kratosclient.NewAPIClient(cfg)
// Start a new registration flow
flow, _, err := client.DefaultApi.CreateSelfServiceRegistrationFlow(context.Background(), api.NewCreateSelfServiceRegistrationFlowOpts().GetMethod("link"))
if err != nil {
panic(err)
}
// The flow contains a verification step with a link
fmt.Println("Follow this link to verify your email:")
for _, step := range flow.GetSteps() {
if step.GetId() == "verification" {
fmt.Println(step.GetUrl())
}
}
}
Running this program outputs a URL that you can visit to trigger the verification step. In a production scenario you would embed this link in the email body configured in the schema.
Testing & Verification
To confirm the flow works end‑to‑end:
- Start the flow via the Go client or a browser request to
/self-service/registration/api?return_to=<URL>. - Check the mailer logs or inbox to retrieve the verification link.
- Click the link (or curl it) and observe that the flow status in the
kratos_flow_registrationtable changes fromstarttocompleted. - Optionally use the SDK’s
GetFlowmethod to fetch the flow and verify that theverificationstep was executed.
Example SQL check:
SELECT status, data FROM kratos_flow_registration WHERE flow_id = 'FLOW_ID';
When the status is completed and the verification step appears in data, the registration succeeded.
Trade‑offs and Limitations
- Mailer dependency: Kratos does not ship an SMTP server. You must provision a reliable mailer; misconfiguration leads to silent failures.
- Token lifetime: The default token validity is 24 h. Shorter lifetimes reduce risk but may inconvenience users; longer lifetimes increase exposure to replay attacks.
- Public URL reachability: The verification link contains a domain that must be reachable by the user. If you run Kratos behind a NAT or proxy without proper routing, the link will fail.
- Database visibility: Directly inspecting
kratos_flow_registrationis useful for debugging but should be avoided in production logs due to potential sensitive data.
Actionable Closing
By declaring a registration flow, configuring a mailer, and using the Go SDK, you can ship a fully‑auditable, email‑verified registration system in under an hour. Remember to:
- Secure your SMTP credentials with a secrets manager.
- Adjust
token_lifetimeinconfig.yamlto match your security policy. - Expose a public HTTPS endpoint that the verification link can reach.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.