Elements Version 3.8+
Elements supports email-based identity verification for user accounts. When a user’s email address is linked to their account via the email scheme, the address starts in the UNVERIFIED state. The verification flow confirms that the user controls that address.
Verification Lifecycle #
UNVERIFIED
│
│ POST /user/me/email/verify
│ (EmailVerificationService.requestVerification)
▼
PENDING
│
│ GET /verify?token=<token>
│ (EmailVerificationService.completeVerification)
▼
VERIFIED
| Status | Meaning |
|---|---|
UNVERIFIED | Default state for a newly created email UID. |
PENDING | Verification email has been sent; awaiting the user to click the link. |
VERIFIED | User has clicked the link and confirmed ownership of the address. |
UIDs created via OIDC or OAuth2 providers are set to VERIFIED immediately – the external provider is the trusted verifier.
REST Endpoints #
Request verification #
POST /user/me/email/verify
Authorization: Bearer <session-secret>
Content-Type: application/json
{
"email": "user@example.com"
}
Sends a verification link to the given address if it is linked to the authenticated user’s account. Moves the UID status from UNVERIFIED to PENDING. Returns the updated UserUid.
Responses
| Status | Condition |
|---|---|
200 | Verification email sent; UID is now PENDING. |
400 | SMTP is not configured (see Email Service). |
403 | Not authenticated, or the email does not belong to the current user. |
404 | The email address is not linked to the account. |
Complete verification #
GET /verify?token=<token>
Public endpoint – no authentication required. Consumes the single-use token from the email link and moves the UID status from PENDING to VERIFIED. Returns the updated UserUid.
Responses
| Status | Condition |
|---|---|
200 | Token accepted; UID is now VERIFIED. |
404 | Token is unknown, already used, or expired (24-hour TTL). |
Security model #
- Single-use: the token is deleted immediately on first successful use. Clicking the link twice returns
404. - Time-limited: tokens expire after 24 hours. MongoDB removes expired token documents automatically via a TTL index on the
expiryfield. - Cryptographically random: the token is a UUID v4 generated by
SecureRandom(122 bits of entropy). A database-nativeObjectIdwas deliberately avoided because it encodes a timestamp, machine ID, and counter, making it partially predictable.
Service interface #
package dev.getelements.elements.sdk.service.user;
public interface EmailVerificationService {
/** Fired on requestVerification; carries the updated UserUid (status PENDING). */
String EMAIL_VERIFICATION_REQUESTED_EVENT = "dev.getelements.email_verification.requested";
/** Fired on completeVerification; carries the updated UserUid (status VERIFIED). */
String EMAIL_VERIFICATION_COMPLETED_EVENT = "dev.getelements.email_verification.completed";
UserUid requestVerification(String email, String verificationBaseUrl);
UserUid completeVerification(String token);
}
Element attributes #
| Attribute key | Default | Description |
|---|---|---|
dev.getelements.elements.verification.base_url | (blank – derived from request) | Override the base URL embedded in the link, e.g. when sitting behind a reverse proxy. |
dev.getelements.elements.verification.email_subject | Verify your email | Subject line for the verification email. |
dev.getelements.elements.verification.email_template | (inline link – see below) | Full HTML body. Must contain {link} as the placeholder for the verification URL. |
Default email template #
<p>Please verify your email address by clicking the link below:</p>
<p><a href="{link}">Verify Email</a></p>
Custom template example #
Override VERIFICATION_EMAIL_TEMPLATE in your Element’s Guice module or via element attributes:
public class MyGameElementModule extends AbstractModule {
@Override
protected void configure() {
bindConstant()
.annotatedWith(Names.named(EmailVerificationService.VERIFICATION_EMAIL_TEMPLATE))
.to("<html><body>"
+ "<h1>Confirm your email</h1>"
+ "<p><a href=\"{link}\">Click here to verify</a></p>"
+ "</body></html>");
}
}
The {link} token is always replaced with the full verification URL before the email is sent.
Using EmailVerificationService in custom Elements #
EmailVerificationService is exported to element child injectors. Inject it to trigger verification programmatically on behalf of any user:
import dev.getelements.elements.sdk.service.user.EmailVerificationService;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import static dev.getelements.elements.sdk.service.Constants.UNSCOPED;
public class MyOnboardingService {
private EmailVerificationService emailVerificationService;
public void sendVerification(String email) {
// UNSCOPED skips the current-user ownership check
emailVerificationService.requestVerification(email, "https://mygame.com/api/rest/verify");
}
@Inject
@Named(UNSCOPED)
public void setEmailVerificationService(EmailVerificationService emailVerificationService) {
this.emailVerificationService = emailVerificationService;
}
}
Note: The @Named(UNSCOPED) qualifier is needed when calling requestVerification from Element code on behalf of any user. Without it, the USER-scoped service is used, which checks that the email belongs to the current session user.
Prerequisites #
Email sending requires SMTP to be configured at the platform level. See Email Service for the full configuration reference.
Events #
Both events carry a UserUid argument accessible from element event listeners.
elementRegistry.onEvent(event -> {
if (EmailVerificationService.EMAIL_VERIFICATION_COMPLETED_EVENT.equals(event.getEventName())) {
final var uid = (UserUid) event.getArguments().get(0);
// uid.getVerificationStatus() == VerificationStatus.VERIFIED
}
});

