Email & Templates

FerrisKey sends transactional email for password resets, magic link sign-in, and email verification. Delivery is configured per realm, and each email type can be pointed at a template of your own.

Configure SMTP

SMTP settings live in the database, on the realm, not in a global environment variable. Two realms in the same deployment can use completely different mail providers.

No global SMTP

SMTP is configured per realm, not globally. See the Configuration guide for the short overview. This page covers the full field reference and API.

SMTP fields

FieldTypeNotes
hostStringSMTP server hostname
portu16Port number (1–65535)
usernameStringSMTP authentication username
passwordStringSMTP authentication password. Write-only: the API never returns it
from_emailStringSender address, must be a valid email
from_nameStringSender display name
encryptionEnumtls | starttls | none

Encryption modes:

ModePortWhen to use
tls465Implicit TLS from the first byte. What most modern providers want
starttls587Plain connection upgraded to TLS. Common on corporate relays
none25 / anyNo encryption. Only on a trusted local network, or for testing

SMTP API endpoints

All endpoints are scoped to a realm and require authentication.

MethodPathDescription
GET/realms/{realm_name}/smtp-configRead the current config (password omitted)
PUT/realms/{realm_name}/smtp-configCreate or replace the config
DELETE/realms/{realm_name}/smtp-configRemove the config

Required permissions: reading the SMTP config requires view access to the realm. Creating, updating, or deleting it requires ManageRealm. There are no SMTP-specific permissions beyond the realm gates.

Configure SMTP in the console

Open Realm Settings

In the left sidebar, select the realm you want to configure. Navigate to Realm Settings.

Go to the Email tab

Click the Email tab. You will see the SMTP configuration form.

Fill in the provider details

Enter the host, port, credentials, sender address and name. Select the encryption mode that matches your provider.

Common provider settings:

ProviderHostPortEncryption
Gmail (App Password)smtp.gmail.com465tls
Mailgunsmtp.mailgun.org587starttls
Resendsmtp.resend.com465tls
Local (MailHog / MailPit)localhost1025none

Save and verify

Click Save. Send a test email from the console to confirm delivery reaches the inbox before enabling email-gated features.

Password is write-only

The API never returns the SMTP password. If you need to rotate credentials, submit a full PUT with the new password included.


Transactional emails

There are three types of transactional email, each gated by a realm toggle.

Email typeIdentifierSent whenRealm toggle required
Password resetreset_passwordA user requests a password reset linkforgot_password_enabled
Magic linkmagic_linkA user requests passwordless email loginmagic_link_enabled
Email verificationemail_verificationA user must verify their email addressemail_verification_enabled

Turning a toggle on in Realm Settings activates the matching flow. Until you assign a template of your own, FerrisKey uses the built-in default.


Customizing templates

Template engine

Templates are rendered by a small interpolation engine written for the purpose, not Handlebars or Tera. Placeholders use double braces:

{{variable_name}}

Every interpolated value is HTML-escaped before insertion (&, <, >, ", '), so a template cannot be turned into an injection vector.

HTML escaping

Values inserted via {{...}} are always HTML-escaped. If a user’s name is Alice <script>, it renders as Alice &lt;script&gt; in the final email, safe to embed in HTML.

Templates are stored as a JSON structure that FerrisKey renders to MJML and then to HTML before sending.

Available variables

These variables are available in every template:

VariableDescription
user.first_nameUser’s first name
user.last_nameUser’s last name
user.emailUser’s email address
expirationExpiry time of the token or link

Each email type also provides one link variable:

Email typeLink variableDescription
reset_passwordreset_linkThe password-reset URL
magic_linkmagic_linkThe passwordless login URL
email_verificationverification_linkThe email verification URL

You can query the available variables for any type without authentication:

GET /email-templates/variables/{email_type}

email_type must be one of: reset_password, magic_link, email_verification.

Example response for reset_password:

{
  "data": [
    { "name": "user.first_name", "description": "User's first name" },
    { "name": "user.last_name", "description": "User's last name" },
    { "name": "user.email", "description": "User's email address" },
    { "name": "expiration", "description": "Expiration time" },
    { "name": "reset_link", "description": "Password reset link" }
  ]
}

Template API endpoints

MethodPathDescription
GET/realms/{realm_name}/email-templatesList all templates for the realm
POST/realms/{realm_name}/email-templatesCreate a new template
GET/realms/{realm_name}/email-templates/{template_id}Get a template by ID
PUT/realms/{realm_name}/email-templates/{template_id}Update a template
DELETE/realms/{realm_name}/email-templates/{template_id}Delete a template

Request body for POST: name, email_type, structure.

Request body for PUT: name, structure (email_type cannot be changed after creation).

Required permissions: ViewEmailTemplates to read. ManageEmailTemplates to create, update, or delete. ManageRealm also grants both.

Linking a template to an email type

Creating a template does not activate it. Point the realm at it by updating realm settings:

Realm setting fieldApplies to
reset_password_template_idreset_password emails
magic_link_template_idmagic_link emails
email_verification_template_idemail_verification emails

Set the field to the template UUID to activate it. Set it to null (or leave it unset) to fall back to the built-in default.


Example

Template snippet

A minimal password-reset template body using placeholder syntax:

<p>Hi {{user.first_name}},</p>

<p>
  Someone requested a password reset for your account (<strong>{{user.email}}</strong>).
  This link expires in {{expiration}}.
</p>

<p>
  <a href="{{reset_link}}">Reset your password</a>
</p>

<p>If you did not request this, you can safely ignore this email.</p>

Variables response

{
  "data": [
    { "name": "user.first_name", "description": "User's first name" },
    { "name": "user.last_name", "description": "User's last name" },
    { "name": "user.email", "description": "User's email address" },
    { "name": "expiration", "description": "Expiration time" },
    { "name": "magic_link", "description": "Magic link URL" }
  ]
}