Complete Guide: How to Customize and Brand Keycloak Email Templates

Posted by

Limited Time Offer!

For Less Than the Cost of a Starbucks Coffee, Access All DevOpsSchool Videos on YouTube Unlimitedly.
Master DevOps, SRE, DevSecOps Skills!

Enroll Now

Introduction

Keycloak is an open-source Identity and Access Management solution that includes built-in email functionality for sending messages like password resets, email verification, and account actions.

However, the default emails are plain and generic.
To create a branded and professional experience (like Wizbrand), you can fully customize Keycloakโ€™s email templates, themes, and SMTP settings.

This tutorial walks you through:

  • Creating a custom Keycloak email theme
  • Branding Wizbrand emails with logos and colors
  • Fixing missing template errors (TemplateNotFoundException)
  • Solving SMTP configuration errors
  • Implementing safe variable usage (linkExpiration)
  • Testing and verifying with real emails

Directory Overview

Assuming a Keycloak (Quarkus-based) installation, your structure should look like this:

KEYCLOAK/
โ”œโ”€โ”€ bin/
โ”‚   โ”œโ”€โ”€ kc.sh
โ”‚   โ”œโ”€โ”€ kc.bat
โ”‚   โ”œโ”€โ”€ kcadm.sh
โ”‚   โ””โ”€โ”€ kcadm.bat
โ”œโ”€โ”€ conf/
โ”‚   โ”œโ”€โ”€ keycloak.conf
โ”‚   โ”œโ”€โ”€ truststores/
โ”‚   โ””โ”€โ”€ README.md
โ”œโ”€โ”€ data/
โ”œโ”€โ”€ lib/
โ”œโ”€โ”€ providers/
โ””โ”€โ”€ themes/
    โ”œโ”€โ”€ base/
    โ””โ”€โ”€ wizbrand-email/

The themes/ folder is where youโ€™ll add your custom Wizbrand email theme.


Step 1: Create Your Custom Theme

Create directories

Inside themes/, create a new folder named wizbrand-email:

mkdir -p themes/wizbrand-email/email/html
mkdir -p themes/wizbrand-email/email/text
mkdir -p themes/wizbrand-email/email/messages

Step 2: Configure theme.properties

File:
themes/wizbrand-email/theme.properties

# Inherit everything from the base theme
parent=base

# Import assets from the common Keycloak theme
import=common/keycloak

# Enable translations
internationalizationEnabled=true

This ensures Keycloak uses your theme but still has access to all default assets.


Step 3: Add Required Template Files

Each email type requires both an HTML and a text version.

Common template names:

email-test.ftl              (SMTP test)
password-reset.ftl          (Reset password)
email-verification.ftl      (Verify email)
execute-actions.ftl         (Admin action emails)

They must exist under:

themes/wizbrand-email/email/html/
themes/wizbrand-email/email/text/

Step 4: Fix Missing Template Errors

You may encounter:

Template not found for name "html/password-reset.ftl"

Fix: Ensure correct folder structure and move templates accordingly:

themes/wizbrand-email/email/html/password-reset.ftl
themes/wizbrand-email/email/text/password-reset.ftl

Step 5: Fix โ€œInvalidReferenceException: expiration missingโ€

Default Keycloak templates use linkExpiration, not expiration.
If you use the wrong variable, emails fail to render.

Fix the variable in both templates:

<strong>${(linkExpiration)!"a short time"}</strong>

8. Step 6: Create Your Branded HTML Templates

A. Email Verification Template (HTML)

File: themes/wizbrand-email/email/html/email-verification.ftl

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${msg("emailVerificationSubject")!"Verify your Wizbrand email"}</title>
  </head>
  <body style="margin:0; padding:0; background-color:#f6f9fc; font-family:Arial,Helvetica,sans-serif; color:#333;">

    <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color:#f6f9fc; padding:30px 0;">
      <tr>
        <td align="center">
          <table role="presentation" width="640" cellspacing="0" cellpadding="0" border="0" style="background-color:#ffffff; border-radius:12px; overflow:hidden; box-shadow:0 2px 6px rgba(0,0,0,0.05);">

            <tr>
              <td align="center" style="padding:30px 0 10px 0;">
                <img src="https://www.wizbrand.com/images/logo.png" alt="Wizbrand" width="160">
              </td>
            </tr>

            <tr>
              <td align="center" style="padding:10px 40px;">
                <h2 style="color:#0b5fff; font-size:22px; font-weight:700; margin:0;">
                  ${msg("emailVerificationSubject")!"Verify your Wizbrand email"}
                </h2>
              </td>
            </tr>

            <tr>
              <td style="padding:20px 40px; font-size:15px; line-height:1.6; color:#444;">
                <p>Hello <strong>${user.username!user.email}</strong>,</p>
                <p>
                  Please verify your email for <strong>${realmName}</strong>. 
                  This link will expire in <strong>${(linkExpiration)!"a short time"}</strong>.
                </p>
              </td>
            </tr>

            <tr>
              <td align="center" style="padding:10px 40px 25px 40px;">
                <a href="${link}" style="background-color:#0b5fff; color:#fff; text-decoration:none; padding:12px 28px; border-radius:6px; display:inline-block; font-weight:600; font-size:15px;">Verify Email</a>
              </td>
            </tr>

            <tr>
              <td style="padding:0 40px 25px 40px; font-size:13px; line-height:1.6; color:#555;">
                <p>If the button doesnโ€™t work, copy this link:</p>
                <p style="word-break:break-all; color:#0b5fff;">${link}</p>
              </td>
            </tr>

            <tr>
              <td style="border-top:1px solid #e5e5e5;"></td>
            </tr>

            <tr>
              <td style="padding:20px 40px 30px 40px; font-size:12px; line-height:1.6; color:#777;">
                <p><strong>Wizbrand</strong></p>
                <p>${kcSanitize(msg("emailFooterHtml"))!"This is an automated email from Wizbrand. Please do not reply."}</p>
                <p>ยฉ ${.now?string("yyyy")} Wizbrand. All rights reserved.</p>
              </td>
            </tr>

          </table>
        </td>
      </tr>
    </table>

  </body>
</html>

B. Email Verification (Text)

File: themes/wizbrand-email/email/text/email-verification.ftl

${msg("emailVerificationSubject")!"Verify your Wizbrand email"}

Hello ${user.username!user.email},

Please verify your email for ${realmName}.
This link expires in ${(linkExpiration)!"a short time"}.

${link}

If you didnโ€™t request this, you can safely ignore this email.

Wizbrand
${kcSanitize(msg("emailFooterHtml"))!"This is an automated email from Wizbrand. Please do not reply."}
ยฉ ${.now?string("yyyy")} Wizbrand

C. Password Reset Template (HTML)

File: themes/wizbrand-email/email/html/password-reset.ftl

<!DOCTYPE html>
<html>
  <body style="margin:0;background:#f7f9fc;font-family:Arial,Helvetica,sans-serif;color:#333;">
    <div style="max-width:640px;margin:24px auto;background:#fff;border-radius:10px;padding:32px;">
      <div style="text-align:center;margin-bottom:20px;">
        <img src="https://www.wizbrand.com/images/logo.png" alt="Wizbrand" style="max-width:180px;height:auto;"/>
      </div>

      <h2 style="margin:0 0 12px;color:#0b5fff;text-align:center;">
        ${msg("passwordResetSubject")}
      </h2>

      <p>Hello <strong>${user.username!user.email}</strong>,</p>
      <p>We received a request to reset your Wizbrand account credentials.</p>
      <p>If this was you, click the button below. This link expires in <strong>${(linkExpiration)!"a short time"}</strong>.</p>

      <p style="text-align:center;margin:24px 0;">
        <a href="${link}" style="display:inline-block;text-decoration:none;padding:12px 22px;border-radius:6px;background:#0b5fff;color:#fff;font-weight:bold;">
          Reset credentials
        </a>
      </p>

      <p>If you didnโ€™t request this, you can safely ignore this email.</p>

      <hr style="border:none;border-top:1px solid #eee;margin:28px 0;"/>
      <div style="font-size:12px;color:#666;line-height:1.6;">
        <div><strong>${realmName}</strong></div>
        <div>${kcSanitize(msg("emailFooterHtml"))}</div>
        <div>ยฉ ${.now?string("yyyy")} Wizbrand</div>
      </div>
    </div>
  </body>
</html>

Step 7: Configure Email Settings in Keycloak Admin Console

  1. Go to Realm Settings โ†’ Email
  2. Enter SMTP details:
FieldExample
Hostsmtp.gmail.com / email-smtp.ap-south-1.amazonaws.com
Port587 (STARTTLS) or 465 (SSL)
Fromno-reply@wizbrand.com
From Display NameWizbrand Team
AuthenticationUsername/Password
Enable Debug SMTPโœ… (for testing)
  1. Click Test Connection
    If successful, youโ€™ll see a test email in your inbox.

Step 8: Troubleshooting Common Errors

ErrorCauseSolution
Template not found for nameFile missing or wrong folderMove it under email/html/ or email/text/
InvalidReferenceException: expiration missingUsed ${expiration} instead of ${linkExpiration}Replace with linkExpiration
Failed to send emailSMTP misconfigurationVerify port, auth, and TLS/SSL
535 Authentication failedWrong credentials or missing App PasswordUse Gmail App Password or SES credentials
PKIX path building failedUntrusted certificateAdd CA cert to JVM truststore
No email receivedSpam folder or unverified From addressVerify domain in SES / Mailgun

Step 9: Apply Theme and Test

  1. Go to Realm Settings โ†’ Themes
  2. Under Email Theme, select wizbrand-email
  3. Save changes
  4. Use Forgot Password or Verify Email to test

Step 10: Best Practices

  • Always provide both HTML and Text templates.
  • Keep all CSS inline for better compatibility.
  • Use absolute URLs for images (e.g., https://www.wizbrand.com/images/logo.png).
  • Use safe defaults for optional variables (e.g., ${(linkExpiration)!"a short time"}).
  • Add message bundles in /email/messages/messages_en.properties.
  • Disable template caching during development: bin/kc.sh start --spi-theme-cache-themes=false --spi-theme-cache-templates=false

Final Result

After completing these steps, your Keycloak emails will look like this:

Verification Email

  • Subject: โ€œVerify your Wizbrand emailโ€
  • Branded with Wizbrand logo and color scheme (#0b5fff)
  • Responsive on mobile and desktop
  • Clean layout with fallback link and footer

Password Reset Email

  • Subject: โ€œReset your Wizbrand credentialsโ€
  • Blue button with rounded edges
  • Expiry information and helpful footer

Bonus: Message Bundle Example

themes/wizbrand-email/email/messages/messages_en.properties

emailVerificationSubject=Verify your Wizbrand email
passwordResetSubject=Reset your Wizbrand credentials
emailFooterHtml=This is an automated email from Wizbrand. Please do not reply.

Summary

StepTaskKey Output
1Create themewizbrand-email
2Add theme.propertiesDefines inheritance
3Add email/html + email/textTemplates
4Replace ${expiration} โ†’ ${linkExpiration}Fix render errors
5Configure SMTPGmail / SES
6Apply ThemeRealm Settings โ†’ Themes
7Test ConnectionConfirm mail delivery
8Refine BrandingAdd logo, footer, color scheme

Conclusion

Youโ€™ve now built a fully branded Keycloak email system for Wizbrand with:

  • Custom logo and colors
  • Fully responsive, professional HTML templates
  • Secure and working SMTP integration
  • Safe, bug-free FreeMarker logic
  • Localization-ready message bundles

Your users will now receive elegant, trustworthy, and brand-consistent emails that match Wizbrandโ€™s identity โ€” improving user experience and trust.


Leave a Reply

Your email address will not be published. Required fields are marked *

0
Would love your thoughts, please comment.x
()
x