Keycloak Email Configuration: SMTP Setup and Templates
Last updated: March 2026
Email is a critical component of any authentication system. Users need verification emails to confirm their accounts, password reset links when they forget credentials, and OTP codes for multi-factor authentication. If email does not work, your users cannot complete these flows, and your support team handles requests that should be automated.
This guide covers SMTP configuration for Keycloak with four popular email providers, custom email template creation using FreeMarker, and troubleshooting common issues.
SMTP Configuration via Admin Console
The quickest way to configure email is through the Keycloak admin console:
- Navigate to Realm Settings > Email.
- Fill in the SMTP settings.
- Click Test Connection to verify.
The fields map to these settings:
| Field | Description |
|---|---|
| From | The sender email address (e.g., [email protected]) |
| From Display Name | The sender name shown in email clients |
| Reply To | Where replies should go (optional) |
| Host | SMTP server hostname |
| Port | SMTP port (usually 587 for STARTTLS, 465 for SSL) |
| Enable SSL | Use implicit SSL (port 465) |
| Enable StartTLS | Use STARTTLS (port 587, recommended) |
| Username | SMTP authentication username |
| Password | SMTP authentication password |
Provider-Specific Configuration
Amazon SES
Amazon Simple Email Service is the most cost-effective option for AWS-hosted Keycloak deployments.
Prerequisites:
- Verify your sending domain in SES.
- Create SMTP credentials in the SES console (these are different from IAM credentials).
- If your SES account is in sandbox mode, verify recipient email addresses for testing.
Keycloak configuration:
# Environment variables
[email protected]
KC_SPI_EMAIL_SENDER_DEFAULT_FROM_DISPLAY_NAME=Your Company
KC_SPI_EMAIL_SENDER_DEFAULT_HOST=email-smtp.us-east-1.amazonaws.com
KC_SPI_EMAIL_SENDER_DEFAULT_PORT=587
KC_SPI_EMAIL_SENDER_DEFAULT_STARTTLS=true
KC_SPI_EMAIL_SENDER_DEFAULT_AUTH=true
KC_SPI_EMAIL_SENDER_DEFAULT_USER=AKIAXXXXXXXXXXXXXXXX
KC_SPI_EMAIL_SENDER_DEFAULT_PASSWORD=your-ses-smtp-password
Or via the Admin REST API:
curl -X PUT -H "Authorization: Bearer $ADMIN_TOKEN"
-H "Content-Type: application/json"
"https://keycloak.example.com/admin/realms/myrealm"
-d '{
"smtpServer": {
"from": "[email protected]",
"fromDisplayName": "Your Company",
"host": "email-smtp.us-east-1.amazonaws.com",
"port": "587",
"starttls": "true",
"auth": "true",
"user": "AKIAXXXXXXXXXXXXXXXX",
"password": "your-ses-smtp-password"
}
}'
SES-specific considerations:
- Use the SMTP endpoint for your SES region (e.g.,
email-smtp.us-east-1.amazonaws.com). - SMTP credentials are NOT IAM access keys. Generate them in the SES console under SMTP Settings.
- Production sending requires moving out of SES sandbox mode. Request production access through the AWS console.
- Set up SNS notifications for bounces and complaints to maintain sender reputation.
SendGrid
SendGrid (now part of Twilio) provides a dedicated SMTP relay with analytics.
Prerequisites:
- Create a SendGrid account and verify your sending domain.
- Generate an API key with Mail Send permissions.
Keycloak configuration:
KC_SPI_EMAIL_SENDER_DEFAULT_HOST=smtp.sendgrid.net
KC_SPI_EMAIL_SENDER_DEFAULT_PORT=587
KC_SPI_EMAIL_SENDER_DEFAULT_STARTTLS=true
KC_SPI_EMAIL_SENDER_DEFAULT_AUTH=true
KC_SPI_EMAIL_SENDER_DEFAULT_USER=apikey
KC_SPI_EMAIL_SENDER_DEFAULT_PASSWORD=SG.xxxxxxxxxxxxxxxxxxxx
Note: The username is literally apikey (not your account name). The password is your SendGrid API key.
Mailgun
Mailgun provides SMTP relay with email validation and analytics.
Prerequisites:
- Add and verify your sending domain in Mailgun.
- Get your SMTP credentials from the domain settings.
Keycloak configuration:
KC_SPI_EMAIL_SENDER_DEFAULT_HOST=smtp.mailgun.org
KC_SPI_EMAIL_SENDER_DEFAULT_PORT=587
KC_SPI_EMAIL_SENDER_DEFAULT_STARTTLS=true
KC_SPI_EMAIL_SENDER_DEFAULT_AUTH=true
[email protected]
KC_SPI_EMAIL_SENDER_DEFAULT_PASSWORD=your-mailgun-smtp-password
Gmail (Development Only)
Gmail works for development and testing but has sending limits (500 emails/day) that make it unsuitable for production.
Prerequisites:
- Enable 2-Step Verification on your Google account.
- Generate an App Password (Google Account > Security > App passwords).
Keycloak configuration:
KC_SPI_EMAIL_SENDER_DEFAULT_HOST=smtp.gmail.com
KC_SPI_EMAIL_SENDER_DEFAULT_PORT=587
KC_SPI_EMAIL_SENDER_DEFAULT_STARTTLS=true
KC_SPI_EMAIL_SENDER_DEFAULT_AUTH=true
[email protected]
KC_SPI_EMAIL_SENDER_DEFAULT_PASSWORD=your-app-password
For local development, consider using Mailpit or MailHog to capture emails without sending them:
# Run Mailpit locally
docker run -p 1025:1025 -p 8025:8025 axllent/mailpit
# Configure Keycloak to use Mailpit
KC_SPI_EMAIL_SENDER_DEFAULT_HOST=localhost
KC_SPI_EMAIL_SENDER_DEFAULT_PORT=1025
KC_SPI_EMAIL_SENDER_DEFAULT_AUTH=false
Access the Mailpit web UI at http://localhost:8025 to view captured emails. For Docker-based local development, use the Keycloak Docker Compose Generator and add Mailpit to the compose file.
Environment Variable Configuration
For production deployments, configure SMTP via environment variables rather than the admin console. This keeps credentials out of the database and enables secrets management integration.
All SMTP settings can be set via environment variables with the prefix KC_SPI_EMAIL_SENDER_DEFAULT_:
# Complete environment variable configuration
[email protected]
KC_SPI_EMAIL_SENDER_DEFAULT_FROM_DISPLAY_NAME=Your Company
[email protected]
KC_SPI_EMAIL_SENDER_DEFAULT_REPLY_TO_DISPLAY_NAME=Support Team
KC_SPI_EMAIL_SENDER_DEFAULT_HOST=smtp.provider.com
KC_SPI_EMAIL_SENDER_DEFAULT_PORT=587
KC_SPI_EMAIL_SENDER_DEFAULT_STARTTLS=true
KC_SPI_EMAIL_SENDER_DEFAULT_SSL=false
KC_SPI_EMAIL_SENDER_DEFAULT_AUTH=true
KC_SPI_EMAIL_SENDER_DEFAULT_USER=smtp-username
KC_SPI_EMAIL_SENDER_DEFAULT_PASSWORD=smtp-password
[email protected]
In Kubernetes, store the SMTP password in a Secret:
apiVersion: v1
kind: Secret
metadata:
name: keycloak-smtp
type: Opaque
stringData:
SMTP_PASSWORD: "your-smtp-password"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
spec:
template:
spec:
containers:
- name: keycloak
env:
- name: KC_SPI_EMAIL_SENDER_DEFAULT_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-smtp
key: SMTP_PASSWORD
Email Template Customization
Keycloak uses FreeMarker templates for email generation. The default templates are functional but generic. Customizing them reinforces your brand and improves user trust.
Template Types
Keycloak includes these email templates:
| Template | Trigger |
|---|---|
email-verification.ftl |
User registers or admin triggers verification |
email-verification-with-code.ftl |
Verification using an OTP code instead of a link |
password-reset.ftl |
User requests a password reset |
executeActions.ftl |
Admin triggers required actions (verify email, update password, etc.) |
email-update-confirmation.ftl |
User changes their email address |
event-login_error.ftl |
Login error notification |
event-remove_totp.ftl |
TOTP removal notification |
event-update_password.ftl |
Password change notification |
event-update_totp.ftl |
TOTP setup notification |
identity-provider-link.ftl |
Identity provider account linking |
org-invite.ftl |
Organization invitation |
Creating Custom Templates
Create a custom email theme directory. See Keycloak Theme Customization for the full theme structure.
Here is a custom email-verification.ftl with branded styling:
<html>
<head>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Helvetica, Arial, sans-serif;
background-color: #f1f5f9;
margin: 0;
padding: 20px;
color: #1e293b;
}
.email-container {
max-width: 560px;
margin: 0 auto;
background: #ffffff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.email-header {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
padding: 32px;
text-align: center;
}
.email-header img {
height: 36px;
}
.email-body {
padding: 32px;
line-height: 1.6;
}
.email-body h2 {
margin-top: 0;
color: #0f172a;
font-size: 22px;
}
.cta-button {
display: inline-block;
padding: 14px 36px;
background: linear-gradient(135deg, #3b82f6, #1e40af);
color: #ffffff !important;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
font-size: 15px;
margin: 24px 0;
}
.link-text {
word-break: break-all;
color: #3b82f6;
font-size: 13px;
}
.email-footer {
padding: 24px 32px;
border-top: 1px solid #e2e8f0;
color: #64748b;
font-size: 13px;
line-height: 1.5;
}
.expiry-note {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 12px 16px;
font-size: 13px;
color: #64748b;
margin-top: 24px;
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-header">
<img src="https://your-domain.com/images/logo-white.svg"
alt="${realmName}" />
</div>
<div class="email-body">
<h2>${msg("emailVerificationSubject")}</h2>
<p>Hi ${user.firstName!"there"},</p>
<p>Thanks for creating your account. To get started, please verify
your email address by clicking the button below.</p>
<p style="text-align: center;">
<a href="${link}" class="cta-button">Verify Email Address</a>
</p>
<p>Or copy and paste this link into your browser:</p>
<p class="link-text">${link}</p>
<div class="expiry-note">
This link will expire in ${linkExpirationFormatter(linkExpiration)}.
If you did not create an account, you can safely ignore this email.
</div>
</div>
<div class="email-footer">
<p>This email was sent by ${realmName}. If you have questions,
contact our support team.</p>
</div>
</div>
</body>
</html>
Password Reset Template
Create password-reset.ftl:
<html>
<head>
<!-- Use the same style block as email-verification.ftl -->
<style>
/* ... same styles as above ... */
</style>
</head>
<body>
<div class="email-container">
<div class="email-header">
<img src="https://your-domain.com/images/logo-white.svg"
alt="${realmName}" />
</div>
<div class="email-body">
<h2>Reset Your Password</h2>
<p>Hi ${user.firstName!"there"},</p>
<p>We received a request to reset the password for your account.
Click the button below to choose a new password.</p>
<p style="text-align: center;">
<a href="${link}" class="cta-button">Reset Password</a>
</p>
<p>Or copy and paste this link into your browser:</p>
<p class="link-text">${link}</p>
<div class="expiry-note">
This link will expire in ${linkExpirationFormatter(linkExpiration)}.
If you did not request a password reset, your account is safe and
you can ignore this email. Someone may have entered your email
address by mistake.
</div>
</div>
<div class="email-footer">
<p>For security, this link can only be used once. If you need to
reset your password again, visit the login page and click
"Forgot Password."</p>
</div>
</div>
</body>
</html>
OTP Email Template
If you use email-based OTP for multi-factor authentication, customize email-verification-with-code.ftl:
<html>
<head>
<style>
/* ... same base styles ... */
.otp-code {
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 36px;
font-weight: 700;
letter-spacing: 8px;
color: #0f172a;
background: #f1f5f9;
border: 2px solid #e2e8f0;
border-radius: 12px;
padding: 20px 32px;
display: inline-block;
margin: 24px 0;
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-header">
<img src="https://your-domain.com/images/logo-white.svg"
alt="${realmName}" />
</div>
<div class="email-body">
<h2>Your Verification Code</h2>
<p>Hi ${user.firstName!"there"},</p>
<p>Use the following code to verify your identity:</p>
<p style="text-align: center;">
<span class="otp-code">${code}</span>
</p>
<div class="expiry-note">
This code will expire in ${linkExpirationFormatter(linkExpiration)}.
If you did not request this code, please secure your account
immediately.
</div>
</div>
<div class="email-footer">
<p>Never share this code with anyone. ${realmName} will never
ask you for this code via phone or chat.</p>
</div>
</div>
</body>
</html>
For using email OTP with Skycloak’s extensions, see Using Email OTP Keycloak Extension with Skycloak.
Custom Message Strings
Override email subject lines and text in messages/messages_en.properties:
# Subject lines
emailVerificationSubject=Verify your email for {0}
passwordResetSubject=Reset your {0} password
emailUpdateConfirmationSubject=Confirm your new email for {0}
executeActionsSubject=Action required for your {0} account
# Common strings
emailVerificationBody=Please verify your email address.
passwordResetBody=Click the link below to reset your password.
Configuring Email-Related Realm Settings
Beyond SMTP configuration, several realm settings affect email behavior:
Verification Email Settings
curl -X PUT -H "Authorization: Bearer $ADMIN_TOKEN"
-H "Content-Type: application/json"
"https://keycloak.example.com/admin/realms/myrealm"
-d '{
"verifyEmail": true,
"loginWithEmailAllowed": true,
"registrationEmailAsUsername": true,
"actionTokenGeneratedByUserLifespan": 300,
"actionTokenGeneratedByAdminLifespan": 43200
}'
| Setting | Description | Recommended |
|---|---|---|
verifyEmail |
Require email verification on registration | true |
loginWithEmailAllowed |
Allow login with email address | true |
registrationEmailAsUsername |
Use email as the username | true for most apps |
actionTokenGeneratedByUserLifespan |
Link expiration for user-triggered emails (seconds) | 300 (5 minutes) |
actionTokenGeneratedByAdminLifespan |
Link expiration for admin-triggered emails (seconds) | 43200 (12 hours) |
Required Actions Configuration
Control which actions trigger emails:
- Verify Email: Sends verification email on registration.
- Update Password: Sends password reset email with link.
- Configure OTP: Does not send email (user configures OTP in-session).
Admin-triggered required actions (via the admin console or API) use the executeActions email template:
# Trigger email verification and password update for a user
curl -X PUT -H "Authorization: Bearer $ADMIN_TOKEN"
"https://keycloak.example.com/admin/realms/myrealm/users/$USER_ID/execute-actions-email"
-H "Content-Type: application/json"
-d '["VERIFY_EMAIL", "UPDATE_PASSWORD"]'
Deploying Custom Email Templates
Volume Mount (Development)
docker run -p 8080:8080
-v /path/to/themes/my-company:/opt/keycloak/themes/my-company
quay.io/keycloak/keycloak:26.1.0 start-dev
Docker Build (Production)
FROM quay.io/keycloak/keycloak:26.1.0 AS builder
COPY themes/my-company /opt/keycloak/themes/my-company
ENV KC_DB=postgres
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:26.1.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
CMD ["start", "--optimized"]
Activate the Email Theme
In the admin console, navigate to Realm Settings > Themes and set Email Theme to my-company.
Or via the API:
curl -X PUT -H "Authorization: Bearer $ADMIN_TOKEN"
-H "Content-Type: application/json"
"https://keycloak.example.com/admin/realms/myrealm"
-d '{"emailTheme": "my-company"}'
Troubleshooting
Connection Refused or Timeout
ERROR: Could not send email: javax.mail.MessagingException: Could not connect to SMTP host
Causes:
- SMTP host or port is incorrect.
- Firewall blocks outbound traffic on port 587 or 465.
- TLS/STARTTLS mismatch (e.g., using STARTTLS on port 465 which expects implicit SSL).
Fix:
# Test SMTP connectivity from the Keycloak container
docker exec keycloak-container nc -zv smtp.provider.com 587
Authentication Failed
ERROR: javax.mail.AuthenticationFailedException: 535 Authentication failed
Causes:
- Wrong username or password.
- For SES: Using IAM credentials instead of SMTP credentials.
- For Gmail: App password not generated or 2-Step Verification not enabled.
- For SendGrid: Using account password instead of API key, or username is not
apikey.
Emails Go to Spam
Causes and fixes:
- SPF record missing. Add an SPF record to your domain’s DNS that includes your SMTP provider.
- DKIM not configured. Most providers (SES, SendGrid, Mailgun) require DKIM setup.
- DMARC not configured. Add a DMARC record after SPF and DKIM are in place.
- “From” address mismatch. The
fromaddress must be from a verified domain. - Generic content. The default Keycloak templates look automated. Custom templates with your brand improve deliverability.
# Check DNS records for your domain
dig TXT example.com +short # SPF
dig TXT default._domainkey.example.com +short # DKIM
dig TXT _dmarc.example.com +short # DMARC
Link Expired Before User Clicks
The default token lifespan may be too short. Increase it:
curl -X PUT -H "Authorization: Bearer $ADMIN_TOKEN"
-H "Content-Type: application/json"
"https://keycloak.example.com/admin/realms/myrealm"
-d '{"actionTokenGeneratedByUserLifespan": 900}'
This sets the lifespan to 15 minutes (900 seconds). Do not set it longer than necessary — these links are single-use security tokens.
Emails Not Sent for Specific Events
Ensure events are enabled in Realm Settings > Events > Event Listeners. The email event listener must be enabled for event-triggered emails (login errors, password changes, TOTP changes).
For monitoring email-related events, configure audit logging and review the SEND_VERIFY_EMAIL, SEND_RESET_PASSWORD, and SEND_IDENTITY_PROVIDER_LINK event types.
Email Best Practices
-
Use a dedicated sending domain (e.g.,
auth.example.com) separate from your marketing email domain. This protects your marketing email reputation if auth emails trigger spam reports. -
Configure SPF, DKIM, and DMARC before going to production. All major email providers require these for reliable delivery.
-
Set reasonable token lifespans. 5-15 minutes for user-triggered actions (password reset). 12-24 hours for admin-triggered actions (account setup).
-
Monitor bounce rates. High bounce rates degrade your sender reputation. Integrate with your SMTP provider’s bounce/complaint notifications.
-
Test across email clients. Email rendering varies across Gmail, Outlook, Apple Mail, and mobile clients. Test your templates with tools like Litmus or Email on Acid.
-
Include plain-text alternatives. Create a
text/directory alongsidehtml/with plain-text versions of each template for clients that do not render HTML. -
Localize templates. Create
messages_xx.propertiesfiles for each language your users need. Keycloak selects the right template based on the user’s locale.
Next Steps
- Keycloak Theme Customization for login and account theme customization
- Is Keycloak Production Ready? A Practical Checklist for comprehensive production setup
- Keycloak Server Administration Guide: Email for official documentation
- Skycloak Branding Feature for managed email template deployment
Want managed email configuration with verified sending domains and template deployment handled for you? Skycloak provides managed Keycloak with email infrastructure included. Check our pricing to learn more.
Ready to simplify your authentication?
Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.