simplify config & templates

This commit is contained in:
Hemachandar
2025-10-06 19:58:01 +05:30
parent 752368327f
commit 29dbe99840
11 changed files with 40 additions and 346 deletions
+1 -40
View File
@@ -9,8 +9,6 @@ use Appwrite\Network\Platform;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
$localeCodes = include __DIR__ . '/locale/codes.php';
$console = [
'$id' => ID::custom('console'),
'$sequence' => ID::custom('console'),
@@ -51,44 +49,7 @@ $console = [
'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
],
'templates' => [
'email.verification-en' => [
'subject' => 'Account Verification',
'preview' => 'Verify your email to activate your {{project}} account.',
'heading' => 'Verify your email to start using Appwrite Cloud',
'hello' => 'Hello {{user}},',
'body' => 'Thanks for signing up for Appwrite Cloud. Before you can get started, please verify your email address.',
'footer' => 'If you didnt create an account, you can ignore this email.',
'buttonText' => 'Verify email',
'thanks' => 'Thanks,',
"signature" => "{{project}} team",
],
'email.mfaChallenge-en' => [
'subject' => 'Verification Code for {{project}}',
'preview' => 'Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.',
'heading' => 'Complete two-step verification to use Appwrite Cloud',
'hello' => 'Hello {{user}},',
'body' => 'Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.',
'thanks' => 'Thanks,',
"signature" => "{{project}} team",
],
'email.otpSession-en' => [
'subject' => 'OTP for {{project}} Login',
'preview' => 'Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.',
'heading' => 'Login with OTP to use Appwrite Cloud',
'hello' => 'Hello {{user}},',
'body' => 'Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.',
'thanks' => 'Thanks,',
"signature" => "{{project}} team",
],
],
'customEmails' => true,
'smtpBaseTemplate' => 'email-base-styled',
];
foreach ($localeCodes as $localeCode) {
$console['templates']['email.verification-' . $localeCode['code']] = $console['templates']['email.verification-en'];
$console['templates']['email.mfaChallenge-' . $localeCode['code']] = $console['templates']['email.mfaChallenge-en'];
$console['templates']['email.otpSession-' . $localeCode['code']] = $console['templates']['email.otpSession-en'];
}
return $console;
@@ -1,225 +0,0 @@
<!doctype html>
<html>
<head>
<link rel="preconnect" href="https://assets.appwrite.io/" crossorigin>
<style>
@font-face {
font-family: 'Inter';
src: url('https://assets.appwrite.io/fonts/inter/Inter-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('https://assets.appwrite.io/fonts/dm-sans/dm-sans-v16-latin-600.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
</style>
<style>
@media (max-width:500px) {
.mobile-full-width {
width: 100%;
}
}
.main a {
color: currentColor;
}
.main {
padding: 32px;
line-height: 1.5;
color: #616b7c;
font-size: 15px;
font-weight: 400;
font-family: "Inter", sans-serif;
background-color: #ffffff;
margin: 0;
padding: 0;
}
a {
color: currentColor;
word-break: break-all;
}
table {
width: 100%;
border-spacing: 0 !important;
}
table, tr, th, td {
margin: 0;
padding: 0;
}
td {
vertical-align: top;
}
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
}
h1 {
font-size: 22px;
margin-bottom: 0px;
margin-top: 0px;
color: #373b4d;
}
h2 {
font-size: 20px;
font-weight: 600;
color: #373b4d;
}
h3 {
font-size: 14px;
font-weight: 500;
color: #373b4d;
line-height: 21px;
margin: 0;
padding: 0;
}
h4 {
font-family: "DM Sans", sans-serif;
font-weight: 600;
font-size: 12px;
color: #4f5769;
margin: 0;
padding: 0;
}
hr {
border: none;
border-top: 1px solid #e8e9f0;
}
</style>
<style>
a.button {
box-sizing: border-box;
display: inline-block;
font-size: 14px;
text-align: center;
text-decoration: none;
color: #ffffff;
border-radius: 8px;
padding: 9px 14px;
background: {{accentColor}};
border-color: {{accentColor}};
border-style: solid;
border-width: 1px;
}
a.button:hover,
a.button:focus {
opacity: 0.8;
}
@media only screen and (max-width: 600px) {
.button {
width: 100%;
}
}
.social-icon {
border-radius: 6px;
background: rgba(216, 216, 219, 0.1);
width: 32px;
height: 32px;
line-height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.social-icon > img {
margin: auto;
}
</style>
</head>
<body>
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div class="main">
<table>
<tr>
<td>
<img
height="32px"
src="{{logoUrl}}"
alt="Appwrite logo"
/>
</td>
</tr>
</table>
<table style="margin-top: 32px">
<tr>
<td>
<h1>{{heading}}</h1>
</td>
</tr>
</table>
<table style="margin-top: 16px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
<table
style="
padding-top: 32px;
margin-top: 32px;
border-top: solid 1px #e8e9f0;
"
>
<tr>
<td></td>
</tr>
</table>
<table style="width: auto; margin: 0 auto">
<tr>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{twitterUrl}}"
class="social-icon"
title="Twitter"
>
<img src="https://cloud.appwrite.io/images/mails/x.png" height="24" width="24" />
</a>
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{discordUrl}}"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" />
</a>
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{githubUrl}}"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" />
</a>
</td>
</tr>
</table>
<table style="width: auto; margin: 0 auto; margin-top: 60px">
<tr>
<td><a href="{{termsUrl}}">Terms</a></td>
<td style="color: #e8e9f0">
<div style="margin: 0 8px">|</div>
</td>
<td><a href="{{privacyUrl}}">Privacy</a></td>
</tr>
</table>
<p style="text-align: center" align="center">
&copy; {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808,
Delaware, United States
</p>
</div>
</body>
</html>
@@ -147,6 +147,7 @@
<img
height="32px"
src="{{logoUrl}}"
alt="Appwrite logo"
/>
</td>
</tr>
@@ -155,12 +156,12 @@
<table style="margin-top: 32px">
<tr>
<td>
<h1>{{subject}}</h1>
<h1>{{heading}}</h1>
</td>
</tr>
</table>
<table style="margin-top: 32px">
<table style="margin-top: 16px">
<tr>
<td>
{{body}}
@@ -44,6 +44,21 @@
color: currentColor;
word-break: break-all;
}
a.button {
box-sizing: border-box;
display: inline-block;
text-align: center;
text-decoration: none;
padding: 9px 14px;
color: #ffffff;
background-color: #2D2D31;
border: 1px solid #414146;
border-radius: 8px;
}
a.button:hover,
a.button:focus {
opacity: 0.8;
}
table {
width: 100%;
border-spacing: 0 !important;
@@ -1,6 +1,6 @@
<p>{{hello}}</p>
<p>{{body}}</p>
<p><a href="{{redirect}}" target="_blank" style="font-size: 14px; font-family: Inter, sans-serif; color: #ffffff; text-decoration: none; background-color: #2D2D31; border-radius: 8px; padding: 9px 14px; border: 1px solid #414146; display: inline-block; text-align:center; box-sizing: border-box;">{{buttonText}}</a></p>
<p><a href="{{redirect}}" target="_blank" class="button">{{buttonText}}</a></p>
<p>{{footer}}</p>
<p style="margin-bottom: 32px">
{{thanks}}
@@ -5,7 +5,7 @@
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #19191D;">
<a rel="noopener" target="_blank" href="{{redirect}}" style="font-size: 14px; font-family: Inter; color: #ffffff; text-decoration: none; border-radius: 8px; padding: 9px 14px; border: 1px solid #19191D; display: inline-block;">{{buttonText}}</a>
<a rel="noopener" target="_blank" href="{{redirect}}" class="button">{{buttonText}}</a>
</td>
</tr>
</table>
@@ -1,9 +0,0 @@
<p>{{hello}}</p>
<p>{{body}}</p>
<p><a href="{{redirect}}" target="_blank" class="button">{{buttonText}}</a></p>
<p>{{footer}}</p>
<p style="margin-bottom: 32px">
{{thanks}}
<br/>
{{signature}}
</p>
+3
View File
@@ -5,6 +5,7 @@
"emails.sender": "{{project}} Team",
"emails.verification.subject": "Account Verification",
"emails.verification.preview": "Verify your email to activate your {{project}} account.",
"emails.verification.heading": "Verify your email to start using Appwrite Cloud",
"emails.verification.hello": "Hello {{user}},",
"emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.",
"emails.verification.footer": "If you didnt ask to verify this address, you can ignore this message.",
@@ -33,6 +34,7 @@
"emails.sessionAlert.signature": "{{project}} team",
"emails.otpSession.subject": "OTP for {{project}} Login",
"emails.otpSession.preview": "Use OTP {{otp}} to sign in to {{project}}. Expires in 15 minutes.",
"emails.otpSession.heading": "Login with OTP to use Appwrite Cloud",
"emails.otpSession.hello": "Hello {{user}},",
"emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.",
"emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.",
@@ -41,6 +43,7 @@
"emails.otpSession.signature": "{{project}} team",
"emails.mfaChallenge.subject": "Verification Code for {{project}}",
"emails.mfaChallenge.preview": "Use code {{otp}} for two-step verification in {{project}}. Expires in 15 minutes.",
"emails.mfaChallenge.heading": "Complete two-step verification to use Appwrite Cloud",
"emails.mfaChallenge.hello": "Hello {{user}},",
"emails.mfaChallenge.description": "Enter the following code to confirm your two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.",
"emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.",
+15 -63
View File
@@ -2296,11 +2296,11 @@ App::post('/v1/account/tokens/email')
$subject = $locale->getText("emails.otpSession.subject");
$preview = $locale->getText("emails.otpSession.preview");
$customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? [];
$heading = $locale->getText("emails.otpSession.heading");
$customEmails = $project->getAttribute('customEmails', false);
$bodyTemplate = '';
$heading = '';
$customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? [];
$smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base');
$bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl';
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$agentOs = $detector->getOS();
@@ -2369,23 +2369,6 @@ App::post('/v1/account/tokens/email')
->setSmtpReplyTo($replyTo)
->setSmtpSenderEmail($senderEmail)
->setSmtpSenderName($senderName);
} elseif ($customEmails && !empty($customTemplate)) {
$subject = $customTemplate['subject'];
$preview = $customTemplate['preview'];
$heading = $customTemplate['heading'];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-otp.tpl');
$message
->setParam('{{hello}}', $customTemplate['hello'])
->setParam('{{description}}', $customTemplate['body'], escapeHtml: false)
->setParam('{{thanks}}', $customTemplate['thanks'])
->setParam('{{signature}}', $customTemplate['signature'])
->setParam('{{clientInfo}}', '')
->setParam('{{securityPhrase}}', '')
->setParam('{{securityPhraseDividerDisplay}}', 'none');
$body = $message->render();
$bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl';
}
$emailVariables = [
@@ -2402,7 +2385,7 @@ App::post('/v1/account/tokens/email')
'team' => '',
];
if ($customEmails && !empty($customTemplate)) {
if ($smtpBaseTemplate === 'email-base-styled') {
$emailVariables = array_merge($emailVariables, [
'heading' => $heading,
'accentColor' => APP_EMAIL_ACCENT_COLOR,
@@ -3624,7 +3607,11 @@ App::post('/v1/account/verification')
$body = $locale->getText("emails.verification.body");
$preview = $locale->getText("emails.verification.preview");
$subject = $locale->getText("emails.verification.subject");
$heading = $locale->getText("emails.verification.heading");
$customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? [];
$smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base');
$bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl';
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
@@ -3644,10 +3631,6 @@ App::post('/v1/account/verification')
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
$replyTo = "";
$customEmails = $project->getAttribute('customEmails', false);
$bodyTemplate = '';
$heading = '';
if ($smtpEnabled) {
if (!empty($smtp['senderEmail'])) {
$senderEmail = $smtp['senderEmail'];
@@ -3685,22 +3668,6 @@ App::post('/v1/account/verification')
->setSmtpReplyTo($replyTo)
->setSmtpSenderEmail($senderEmail)
->setSmtpSenderName($senderName);
} elseif ($customEmails && !empty($customTemplate)) {
$subject = $customTemplate['subject'];
$preview = $customTemplate['preview'];
$heading = $customTemplate['heading'];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-verification.tpl');
$message
->setParam('{{hello}}', $customTemplate['hello'])
->setParam('{{body}}', $customTemplate['body'], escapeHtml: false)
->setParam('{{buttonText}}', $customTemplate['buttonText'])
->setParam('{{footer}}', $customTemplate['footer'])
->setParam('{{thanks}}', $customTemplate['thanks'])
->setParam('{{signature}}', $customTemplate['signature']);
$body = $message->render();
$bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl';
}
$emailVariables = [
@@ -3713,7 +3680,7 @@ App::post('/v1/account/verification')
'team' => '',
];
if ($customEmails && !empty($customTemplate)) {
if ($smtpBaseTemplate === 'email-base-styled') {
$emailVariables = array_merge($emailVariables, [
'heading' => $heading,
'accentColor' => APP_EMAIL_ACCENT_COLOR,
@@ -4736,7 +4703,11 @@ App::post('/v1/account/mfa/challenge')
$subject = $locale->getText("emails.mfaChallenge.subject");
$preview = $locale->getText("emails.mfaChallenge.preview");
$heading = $locale->getText("emails.mfaChallenge.heading");
$customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? [];
$smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base');
$bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl';
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$agentOs = $detector->getOS();
@@ -4760,10 +4731,6 @@ App::post('/v1/account/mfa/challenge')
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
$replyTo = "";
$customEmails = $project->getAttribute('customEmails', false);
$bodyTemplate = '';
$heading = '';
if ($smtpEnabled) {
if (!empty($smtp['senderEmail'])) {
$senderEmail = $smtp['senderEmail'];
@@ -4801,21 +4768,6 @@ App::post('/v1/account/mfa/challenge')
->setSmtpReplyTo($replyTo)
->setSmtpSenderEmail($senderEmail)
->setSmtpSenderName($senderName);
} elseif ($customEmails && !empty($customTemplate)) {
$subject = $customTemplate['subject'];
$preview = $customTemplate['preview'];
$heading = $customTemplate['heading'];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-challenge.tpl');
$message
->setParam('{{hello}}', $customTemplate['hello'])
->setParam('{{description}}', $customTemplate['body'], escapeHtml: false)
->setParam('{{thanks}}', $customTemplate['thanks'])
->setParam('{{signature}}', $customTemplate['signature'])
->setParam('{{clientInfo}}', '');
$body = $message->render();
$bodyTemplate = __DIR__ . '/../../config/locale/templates/email-auth-styled.tpl';
}
$emailVariables = [
@@ -4829,7 +4781,7 @@ App::post('/v1/account/mfa/challenge')
'agentOs' => $agentOs['osName'] ?? 'UNKNOWN',
];
if ($customEmails && !empty($customTemplate)) {
if ($smtpBaseTemplate === 'email-base-styled') {
$emailVariables = array_merge($emailVariables, [
'heading' => $heading,
'accentColor' => APP_EMAIL_ACCENT_COLOR,
+1
View File
@@ -82,6 +82,7 @@ class Mails extends Action
$preview = $payload['preview'] ?? '';
$variables['subject'] = $subject;
$variables['heading'] = $variables['heading'] ?? $subject;
$variables['year'] = date("Y");
$attachment = $payload['attachment'] ?? [];
@@ -192,11 +192,6 @@ trait AccountBase
$this->assertStringNotContainsStringIgnoringCase('Appwrite logo', $lastEmail['html']);
}
// TODO: Remove this once OTP login is supported for Console.
if ($isConsoleProject) {
return;
}
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',