diff --git a/app/config/locale/templates/email-smtp-test.tpl b/app/config/locale/templates/email-smtp-test.tpl
index e40b7ba5c8..1b1eccdb7c 100644
--- a/app/config/locale/templates/email-smtp-test.tpl
+++ b/app/config/locale/templates/email-smtp-test.tpl
@@ -9,4 +9,4 @@
If you have trouble with the sender's image, ensure it is set in the Gravatar database.
Best regards,
-Appwrtite team
\ No newline at end of file
+Appwrite team
\ No newline at end of file
diff --git a/app/config/locale/templates/email-webhook-failed.tpl b/app/config/locale/templates/email-webhook-failed.tpl
index 921af9ee29..a176de5754 100644
--- a/app/config/locale/templates/email-webhook-failed.tpl
+++ b/app/config/locale/templates/email-webhook-failed.tpl
@@ -14,7 +14,7 @@
\ No newline at end of file
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index 410ce548f1..6adbf61d6d 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -133,6 +133,16 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
->setSmtpSenderName($senderName);
}
+ // session alerts should always have a client name!
+ $clientName = $session->getAttribute('clientName');
+ if (empty($clientName)) {
+ // fallback to the user agent and then unknown!
+ $userAgent = $session->getAttribute('userAgent');
+ $clientName = !empty($userAgent) ? $userAgent : 'UNKNOWN';
+
+ $session->setAttribute('clientName', $clientName);
+ }
+
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
'date' => (new \DateTime())->format('F j'),
diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php
index e583503237..6f8e2d45b8 100644
--- a/app/controllers/api/projects.php
+++ b/app/controllers/api/projects.php
@@ -2175,7 +2175,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
->setSmtpSenderName($senderName)
->setRecipient($email)
->setName('')
- ->setbodyTemplate(__DIR__ . '/../../config/locale/templates/email-base-styled.tpl')
+ ->setBodyTemplate(__DIR__ . '/../../config/locale/templates/email-base-styled.tpl')
->setBody($template->render())
->setVariables([])
->setSubject($subject)
@@ -2268,16 +2268,53 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
$localeObj = new Locale($locale);
if (is_null($template)) {
- $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
+ /**
+ * different templates, different placeholders.
+ */
+ $templateConfigs = [
+ 'magicSession' => [
+ 'file' => 'email-magic-url.tpl',
+ 'placeholders' => ['optionButton', 'buttonText', 'optionUrl', 'clientInfo', 'securityPhrase']
+ ],
+ 'mfaChallenge' => [
+ 'file' => 'email-mfa-challenge.tpl',
+ 'placeholders' => ['description', 'clientInfo']
+ ],
+ 'otpSession' => [
+ 'file' => 'email-otp.tpl',
+ 'placeholders' => ['description', 'clientInfo', 'securityPhrase']
+ ],
+ 'sessionAlert' => [
+ 'file' => 'email-session-alert.tpl',
+ 'placeholders' => ['body', 'listDevice', 'listIpAddress', 'listCountry', 'footer']
+ ],
+ ];
+
+ // fallback to the base template.
+ $config = $templateConfigs[$type] ?? [
+ 'file' => 'email-inner-base.tpl',
+ 'placeholders' => ['buttonText', 'body', 'footer']
+ ];
+
+ $templateString = file_get_contents(__DIR__ . '/../../config/locale/templates/' . $config['file']);
+
+ // We use `fromString` due to the replace above
+ $message = Template::fromString($templateString);
+
+ // Set type-specific parameters
+ foreach ($config['placeholders'] as $param) {
+ $escapeHtml = !in_array($param, ['clientInfo', 'body', 'footer', 'description']);
+ $message->setParam("{{{$param}}}", $localeObj->getText("emails.{$type}.{$param}"), escapeHtml: $escapeHtml);
+ }
+
$message
+ // common placeholders on all the templates
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
- ->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
- ->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escapeHtml: false)
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
- ->setParam('{{buttonText}}', $localeObj->getText("emails.{$type}.buttonText"))
- ->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature"))
- ->setParam('{{direction}}', $localeObj->getText('settings.direction'));
- $message = $message->render();
+ ->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature"));
+
+ // `useContent: false` will strip new lines!
+ $message = $message->render(useContent: true);
$template = [
'message' => $message,
diff --git a/src/Appwrite/Event/Mail.php b/src/Appwrite/Event/Mail.php
index c9a0671461..aaaa148fde 100644
--- a/src/Appwrite/Event/Mail.php
+++ b/src/Appwrite/Event/Mail.php
@@ -145,7 +145,7 @@ class Mail extends Event
* @param string $bodyTemplate
* @return self
*/
- public function setbodyTemplate(string $bodyTemplate): self
+ public function setBodyTemplate(string $bodyTemplate): self
{
$this->bodyTemplate = $bodyTemplate;
@@ -157,7 +157,7 @@ class Mail extends Event
*
* @return string
*/
- public function getbodyTemplate(): string
+ public function getBodyTemplate(): string
{
return $this->bodyTemplate;
}
diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php
index a2ffba69c1..207f95ff7d 100644
--- a/src/Appwrite/Platform/Workers/Certificates.php
+++ b/src/Appwrite/Platform/Workers/Certificates.php
@@ -389,7 +389,7 @@ class Certificates extends Action
->setPreview($preview)
->setBody($body)
->setName('Appwrite Administrator')
- ->setbodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
+ ->setBodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
->setVariables($emailVariables)
->setRecipient(System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')))
->trigger();
diff --git a/src/Appwrite/Template/Template.php b/src/Appwrite/Template/Template.php
index c01d54389b..e0568c98e9 100644
--- a/src/Appwrite/Template/Template.php
+++ b/src/Appwrite/Template/Template.php
@@ -63,7 +63,7 @@ class Template extends View
*
* @throws Exception
*/
- public function render($minify = true): string
+ public function render($minify = true, $useContent = false): string
{
if ($this->rendered) { // Don't render any template
return '';
@@ -72,7 +72,7 @@ class Template extends View
if (\is_readable($this->path)) {
$template = \file_get_contents($this->path); // Include template file
} elseif (!empty($this->content)) {
- $template = $this->print($this->content, self::FILTER_NL2P);
+ $template = !$useContent ? $this->print($this->content, self::FILTER_NL2P) : $this->content;
} else {
throw new Exception('"' . $this->path . '" template is not readable or not found');
}