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

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
- Go to Realm Settings โ Email
- Enter SMTP details:
Field | Example |
---|---|
Host | smtp.gmail.com / email-smtp.ap-south-1.amazonaws.com |
Port | 587 (STARTTLS) or 465 (SSL) |
From | no-reply@wizbrand.com |
From Display Name | Wizbrand Team |
Authentication | Username/Password |
Enable Debug SMTP | โ (for testing) |
- Click Test Connection
If successful, youโll see a test email in your inbox.
Step 8: Troubleshooting Common Errors
Error | Cause | Solution |
---|---|---|
Template not found for name | File missing or wrong folder | Move it under email/html/ or email/text/ |
InvalidReferenceException: expiration missing | Used ${expiration} instead of ${linkExpiration} | Replace with linkExpiration |
Failed to send email | SMTP misconfiguration | Verify port, auth, and TLS/SSL |
535 Authentication failed | Wrong credentials or missing App Password | Use Gmail App Password or SES credentials |
PKIX path building failed | Untrusted certificate | Add CA cert to JVM truststore |
No email received | Spam folder or unverified From address | Verify domain in SES / Mailgun |
Step 9: Apply Theme and Test
- Go to Realm Settings โ Themes
- Under Email Theme, select
wizbrand-email
- Save changes
- 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
Step | Task | Key Output |
---|---|---|
1 | Create theme | wizbrand-email |
2 | Add theme.properties | Defines inheritance |
3 | Add email/html + email/text | Templates |
4 | Replace ${expiration} โ ${linkExpiration} | Fix render errors |
5 | Configure SMTP | Gmail / SES |
6 | Apply Theme | Realm Settings โ Themes |
7 | Test Connection | Confirm mail delivery |
8 | Refine Branding | Add 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