Email & Templates

FerrisKey sends transactional emails for password resets, magic link authentication, and email verification. Delivery is configured per realm through a dedicated SMTP setup, and each email type can be customized with your own HTML template.

Configure SMTP

SMTP is stored in the database at the realm level — not as a global environment variable. Each realm can use a different mail provider independently.

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, never returned by the API
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 — preferred for modern providers
starttls587Plain connection upgraded to TLS — common in corporate relays
none25 / anyNo encryption — use 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 SMTP config requires view access to the realm. Creating, updating, or deleting requires ManageRealm. There are no dedicated SMTP permissions beyond the realm permission 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

FerrisKey sends exactly three types of transactional email. Each is tied to a realm feature 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

Enabling these toggles in Realm Settings activates the corresponding flow. FerrisKey will use the default built-in template unless you assign a custom one.


Customizing Templates

Template Engine

FerrisKey uses a lightweight custom interpolation engine — not Handlebars or Tera. Placeholders use double-brace syntax:

{{variable_name}}

All interpolated values are HTML-escaped before insertion (&, <, >, ", '), which prevents injection attacks in the rendered email.

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 automatically. You must link it to the realm by updating the 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" }
  ]
}