Migrating Auth Email Templates
If you're moving to Zyphr from another auth provider, you most likely already have branded transactional emails for password reset, magic link, and email verification. You shouldn't have to rebuild those from scratch.
This guide shows how to import your existing templates via the Zyphr API in a single round-trip per application.
What you need
- A Zyphr application public key and secret key (
za_live_pub_...andza_live_sec_...). - Your existing email HTML — it doesn't matter what tool generated it. If it was MJML, even better — MJML is supported natively.
- The variable names your old provider used (you'll remap them to Zyphr's variable contract).
Variable mapping
Zyphr's auth emails take a fixed set of variables per email type. Adjust your imported templates to reference them.
From Auth0
| Auth0 variable | Zyphr variable |
|---|---|
{{ application.name }} | {{application_name}} |
{{ url }} (in password change email) | {{reset_url}} |
{{ url }} (in magic link email) | {{magic_link_url}} |
{{ url }} (in verification email) | {{verify_url}} |
{{ user.email }} | {{user_email}} |
{{ user.user_metadata.name }} | {{user_name}} |
From Firebase Authentication
| Firebase variable | Zyphr variable |
|---|---|
%APP_NAME% | {{application_name}} |
%LINK% (in password reset email) | {{reset_url}} |
%LINK% (in email verification) | {{verify_url}} |
%EMAIL% | {{user_email}} |
%DISPLAY_NAME% | {{user_name}} |
From Clerk
| Clerk variable | Zyphr variable |
|---|---|
{{app.name}} | {{application_name}} |
{{action_link}} (magic link) | {{magic_link_url}} |
{{action_link}} (password reset) | {{reset_url}} |
{{otp_code}} | (use a magic link instead — Zyphr templates render URLs, not codes) |
{{user.email_address}} | {{user_email}} |
From AWS Cognito
| Cognito variable | Zyphr variable |
|---|---|
{####} (verification code) | (use a magic/reset link instead) |
{username} | {{user_email}} |
Bulk import: one curl, all three templates
The bulk endpoint validates and saves all three templates atomically. If any one fails MJML compile or variable validation, none are saved — safe to retry after fixing the one that broke.
curl -X PUT https://api.zyphr.dev/v1/auth/email-templates \
-H "Content-Type: application/json" \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY" \
-d @templates.json
Where templates.json looks like:
{
"magic_link": {
"subject": "Sign in to {{application_name}}",
"html": "<!doctype html><html>...your branded HTML using {{magic_link_url}}...</html>",
"text": "Sign in: {{magic_link_url}}\n\nExpires in {{expires_minutes}} minutes.",
"from_name": "Acme Security",
"reply_to": "support@acme.com"
},
"password_reset": {
"subject": "Reset your {{application_name}} password",
"html": "...{{reset_url}}...",
"text": "Reset your password: {{reset_url}}"
},
"email_verification": {
"subject": "Verify your email",
"html": "...{{verify_url}}...",
"text": "Verify your email: {{verify_url}}"
}
}
A successful response returns the saved templates keyed by type. A failure response includes structured details:
{
"error": {
"code": "mjml_compile_failed",
"message": "MJML compilation failed",
"details": [
{ "line": 12, "message": "Unknown tag mj-foo", "tagName": "mj-foo" }
]
}
}
MJML import
If your existing emails are MJML, send the source in mjml_source instead of html. Zyphr compiles to HTML on save and stores both:
{
"password_reset": {
"subject": "Reset your password",
"mjml_source": "<mjml><mj-body>...{{reset_url}}...</mj-body></mjml>",
"text": "Reset your password: {{reset_url}}"
}
}
Fork the system default
If you don't have an existing template and just want to start from Zyphr's default and tweak it:
curl https://api.zyphr.dev/v1/auth/email-templates/password_reset/default \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY" \
> my_template.json
# edit my_template.json...
curl -X PUT https://api.zyphr.dev/v1/auth/email-templates/password_reset \
-H "Content-Type: application/json" \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY" \
-d @my_template.json
Verify before going live
Before flipping users over to Zyphr, preview and test send each template:
# Render with sample variables — returns subject/html/text, no send
curl -X POST https://api.zyphr.dev/v1/auth/email-templates/password_reset/preview \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY"
# Send a real test email (recipient must be in test_recipients allowlist)
curl -X POST https://api.zyphr.dev/v1/auth/email-templates/password_reset/test \
-H "Content-Type: application/json" \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY" \
-d '{ "to": "you@yourcompany.com" }'
Add addresses to applications.test_recipients via PATCH /v1/applications/:id (dashboard JWT) before calling the test endpoint.
Rollback
To revert a single type to the Zyphr system default:
curl -X DELETE https://api.zyphr.dev/v1/auth/email-templates/password_reset \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY"
To roll back to a previous version (after an update you regret):
# List versions
curl https://api.zyphr.dev/v1/auth/email-templates/password_reset/versions \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY"
# Restore version 3
curl -X POST https://api.zyphr.dev/v1/auth/email-templates/password_reset/versions/3/restore \
-H "X-Application-Key: $ZYPHR_PUBLIC_KEY" \
-H "X-Application-Secret: $ZYPHR_SECRET_KEY"
Project brand defaults
If you're not ready to author full templates and just want Zyphr's defaults to match your brand colors and logo, set the project-level brand defaults instead. The system defaults will pick them up automatically:
curl -X PATCH https://api.zyphr.dev/v1/projects/$PROJECT_ID/brand \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DASHBOARD_JWT" \
-d '{
"brand_primary_color": "#FF6B35",
"brand_secondary_color": "#004E64",
"brand_font_family": "Inter, -apple-system, sans-serif",
"brand_logo_url": "https://yourbrand.com/logo.png"
}'
This works for accounts with multiple brands — set brand defaults independently on each project.