AUTH4D-150 verification email wording
Developers can set four plain-text fields for the verification code emails that an application
sends: the sender display name, the subject, a short intro and a footer. They are read and updated
through the application routes from AUTH4D-18 (repository file docs/api-spec/auth4d-18.md), next to branding and
device_settings from AUTH4D-56 (repository file docs/api-spec/auth4d-56.md). The wire shapes are VerificationEmailSchema and
VerificationEmailUpdateSchema in packages/contracts. Values are stored in the clients columns
added by migration 0013. Browser and game applications both have them.
The verification code, the line that introduces it, and the purpose, expiry and “ignore this email”
sentence are fixed and cannot be replaced. The sender address is the deployment’s EMAIL_FROM
address and never changes; only the display name in front of it does. A project’s own sending domain is covered by AUTH4D-168 (repository file docs/api-spec/auth4d-168.md).
Read
GET /management/tenants/{tenantId}/clients/{clientId} (scope client:read, any project role)
{
"client": { "clientId": "cli_…", "clientType": "native", "…": "…" },
"branding": { "support_url": "https://help.example.com/" },
"device_settings": { "user_code_length": 8, "code_ttl_seconds": 600, "poll_interval_seconds": 5 },
"verification_email": {
"sender_name": "Starfall Arena",
"subject": "Your Starfall Arena sign-in code",
"intro": "Welcome back, pilot!",
"footer": "Starfall Arena is made by Example Games Ltd."
}
}
verification_email contains only the configured fields. An application without wording returns
{} and its emails use the built-in text. The list route (GET …/clients) is unchanged.
Update
PATCH (or PUT) /management/tenants/{tenantId}/clients/{clientId} (scope client:write,
project role owner or admin; a viewer gets 403 insufficient_role)
{ "verification_email": { "subject": "Your Starfall Arena sign-in code", "footer": null } }
- The object is optional and partial, and it can be combined with
branding,device_settingsand the AUTH4D-18 fields. Omitted fields stay unchanged,nullclears a field, and an object that is present must contain at least one field. - Leading and trailing whitespace is trimmed before validation and storage.
- The response has the same shape as the read response and is sent with
no-store. - Changes are committed in the same D1 batch as the other presentation fields, writing only the columns that change. Re-sending the same values is a no-op and writes no audit event.
| Field | Limit | Extra rule |
|---|---|---|
sender_name |
1–64 chars | No < > { } " \ @ , ; : ( ) [ ] |
subject |
1–100 chars | No < > { } |
intro |
1–300 chars | No < > { } |
footer |
1–300 chars | No < > { } |
Every field must also be a single line without control, line-separator or text-direction
characters, must not contain a link (:// or www.), and must not contain six or more digits in a
row, so the text cannot pass for a verification code. Any failure returns 400 validation_failed
with details[].field set to verification_email.<field>, and nothing is written. The D1 CHECKs
repeat the length limits and the forbidden characters.
Rendering
packages/email renders the email. The wording is plain text: it is escaped in the HTML part, kept
literal in the text part and never interpreted as a template. The layout, shared with the console
preview through packages/email/src/verification-text.ts, is:
intro, if setauth4.dev verification code: 381042(fixed)Use this code to sign in to {application}. This code expires in N minutes. If you did not request it, you can ignore this email.(fixed)Need help? Contact {application} support: {support_url}, if a support URL is set (AUTH4D-62)footer, if set
subject replaces the default subject auth4.dev verification code. sender_name becomes the
quoted display name of the From header, for example "Starfall Arena" <noreply@noreply.auth4.dev>.
The API reads the wording by tenant and client when it enqueues the email and carries it inside the encrypted outbox payload. Delivery checks every field against the contract again and drops any field that fails, so a bad stored value falls back to the built-in text rather than blocking sign-in. If long non-ASCII wording would make the encrypted payload exceed the outbox limit, the wording is dropped for that email and the built-in text is used. Control-plane (console) sign-in emails never use customer wording. A database without migration 0013 reads as unconfigured.
Audit
A change writes the existing management.client.presentation.updated event. changed_fields names
the fields, for example verification_email.intro,verification_email.subject. The wording itself is
never recorded.
Follow-up
Sending from a project’s own domain is covered by AUTH4D-168 (repository file docs/api-spec/auth4d-168.md).
Source: docs/api-spec/auth4d-150.md