Merge pull request #9745 from appwrite/feat-logo-url

feat: plan based email logoUrl
This commit is contained in:
Eldad A. Fux 2025-05-15 11:06:07 +02:00 committed by GitHub
commit 919c427d36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 64 additions and 20 deletions

View file

@ -79,7 +79,7 @@
<style>
a.button {
display: inline-block;
background: #fd366e;
background: {{accentColor}};
color: #ffffff;
border-radius: 8px;
height: 48px;
@ -88,7 +88,7 @@
cursor: pointer;
text-align: center;
text-decoration: none;
border-color: #fd366e;
border-color: {{accentColor}};
border-style: solid;
border-width: 1px;
margin-right: 24px;
@ -126,7 +126,7 @@
<td>
<img
height="32px"
src="https://cloud.appwrite.io/images/mails/logo.png"
src="{{logoUrl}}"
/>
</td>
</tr>
@ -164,7 +164,7 @@
<tr>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="https://twitter.com/appwrite"
href="{{twitterUrl}}"
class="social-icon"
title="Twitter"
>
@ -173,7 +173,7 @@
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="https://appwrite.io/discord"
href="{{discordUrl}}"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" />
@ -181,7 +181,7 @@
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="https://github.com/appwrite/appwrite"
href="{{githubUrl}}"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" />
@ -191,11 +191,11 @@
</table>
<table style="width: auto; margin: 0 auto; margin-top: 60px">
<tr>
<td><a href="https://appwrite.io/terms">Terms</a></td>
<td><a href="{{termsUrl}}">Terms</a></td>
<td style="color: #e8e9f0">
<div style="margin: 0 8px">|</div>
</td>
<td><a href="https://appwrite.io/privacy">Privacy</a></td>
<td><a href="{{privacyUrl}}">Privacy</a></td>
</tr>
</table>
<p style="text-align: center" align="center">

View file

@ -2139,7 +2139,8 @@ App::post('/v1/projects/:projectId/smtp/tests')
->inject('response')
->inject('dbForPlatform')
->inject('queueForMails')
->action(function (string $projectId, array $emails, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForPlatform, Mail $queueForMails) {
->inject('plan')
->action(function (string $projectId, array $emails, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForPlatform, Mail $queueForMails, array $plan) {
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@ -2152,7 +2153,14 @@ App::post('/v1/projects/:projectId/smtp/tests')
$template = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-smtp-test.tpl');
$template
->setParam('{{from}}', "{$senderName} ({$senderEmail})")
->setParam('{{replyTo}}', "{$senderName} ({$replyToEmail})");
->setParam('{{replyTo}}', "{$senderName} ({$replyToEmail})")
->setParam('{{logoUrl}}', $plan['logoUrl'] ?? APP_EMAIL_LOGO_URL)
->setParam('{{accentColor}}', $plan['accentColor'] ?? APP_EMAIL_ACCENT_COLOR)
->setParam('{{twitterUrl}}', $plan['twitterUrl'] ?? APP_SOCIAL_TWITTER)
->setParam('{{discordUrl}}', $plan['discordUrl'] ?? APP_SOCIAL_DISCORD)
->setParam('{{githubUrl}}', $plan['githubUrl'] ?? APP_SOCIAL_GITHUB_APPWRITE)
->setParam('{{termsUrl}}', $plan['termsUrl'] ?? APP_EMAIL_TERMS_URL)
->setParam('{{privacyUrl}}', $plan['privacyUrl'] ?? APP_EMAIL_PRIVACY_URL);
foreach ($emails as $email) {
$queueForMails

View file

@ -6,6 +6,10 @@ const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = ''; // Default security email address
const APP_EMAIL_LOGO_URL = 'https://cloud.appwrite.io/images/mails/logo.png';
const APP_EMAIL_ACCENT_COLOR = '#fd366e';
const APP_EMAIL_TERMS_URL = 'https://appwrite.io/terms';
const APP_EMAIL_PRIVACY_URL = 'https://appwrite.io/privacy';
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
const APP_MODE_DEFAULT = 'default';
const APP_MODE_ADMIN = 'admin';
@ -62,6 +66,7 @@ const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
const APP_SOCIAL_GITHUB_APPWRITE = 'https://github.com/appwrite/appwrite';
const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';

View file

@ -363,6 +363,10 @@ Server::setResource(
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
Server::setResource('plan', function (array $plan = []) {
return [];
});
Server::setResource('certificates', function () {
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
if (empty($email)) {

View file

@ -54,6 +54,7 @@ class Certificates extends Action
->inject('queueForRealtime')
->inject('log')
->inject('certificates')
->inject('plan')
->callback([$this, 'action']);
}
@ -80,7 +81,8 @@ class Certificates extends Action
Func $queueForFunctions,
Realtime $queueForRealtime,
Log $log,
CertificatesAdapter $certificates
CertificatesAdapter $certificates,
array $plan
): void {
$payload = $message->getPayload() ?? [];
@ -94,7 +96,7 @@ class Certificates extends Action
$log->addTag('domain', $domain->get());
$this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck);
$this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck, $plan);
}
/**
@ -106,6 +108,7 @@ class Certificates extends Action
* @param Realtime $queueForRealtime
* @param CertificatesAdapter $certificates
* @param bool $skipRenewCheck
* @param array $plan
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
@ -120,7 +123,8 @@ class Certificates extends Action
Realtime $queueForRealtime,
Log $log,
CertificatesAdapter $certificates,
bool $skipRenewCheck = false
bool $skipRenewCheck = false,
array $plan = []
): void {
/**
* 1. Read arguments and validate domain
@ -202,7 +206,7 @@ class Certificates extends Action
$certificate->setAttribute('renewDate', DateTime::now());
// Send email to security email
$this->notifyError($domain->get(), $e->getMessage(), $attempts, $queueForMails);
$this->notifyError($domain->get(), $e->getMessage(), $attempts, $queueForMails, $plan);
throw $e;
} finally {
@ -342,10 +346,11 @@ class Certificates extends Action
* @param string $errorMessage Verbose error message
* @param int $attempt How many times it failed already
* @param Mail $queueForMails
* @param array $plan
* @return void
* @throws Exception
*/
private function notifyError(string $domain, string $errorMessage, int $attempt, Mail $queueForMails): void
private function notifyError(string $domain, string $errorMessage, int $attempt, Mail $queueForMails, array $plan): void
{
// Log error into console
Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage);
@ -357,6 +362,15 @@ class Certificates extends Action
$template->setParam('{{domain}}', $domain);
$template->setParam('{{error}}', \nl2br($errorMessage));
$template->setParam('{{attempts}}', $attempt);
$template->setParam('{{logoUrl}}', $plan['logoUrl'] ?? APP_EMAIL_LOGO_URL);
$template->setParam('{{accentColor}}', $plan['accentColor'] ?? APP_EMAIL_ACCENT_COLOR);
$template->setParam('{{twitterUrl}}', $plan['twitterUrl'] ?? APP_SOCIAL_TWITTER);
$template->setParam('{{discordUrl}}', $plan['discordUrl'] ?? APP_SOCIAL_DISCORD);
$template->setParam('{{githubUrl}}', $plan['githubUrl'] ?? APP_SOCIAL_GITHUB_APPWRITE);
$template->setParam('{{termsUrl}}', $plan['termsUrl'] ?? APP_EMAIL_TERMS_URL);
$template->setParam('{{privacyUrl}}', $plan['privacyUrl'] ?? APP_EMAIL_PRIVACY_URL);
$body = $template->render();
$emailVariables = [

View file

@ -37,6 +37,7 @@ class Webhooks extends Action
->inject('queueForMails')
->inject('queueForStatsUsage')
->inject('log')
->inject('plan')
->callback([$this, 'action']);
}
@ -45,11 +46,13 @@ class Webhooks extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Mail $queueForMails
* @param StatsUsage $queueForStatsUsage
* @param Log $log
* @param array $plan
* @return void
* @throws Exception
*/
public function action(Message $message, Document $project, Database $dbForPlatform, Mail $queueForMails, StatsUsage $queueForStatsUsage, Log $log): void
public function action(Message $message, Document $project, Database $dbForPlatform, Mail $queueForMails, StatsUsage $queueForStatsUsage, Log $log, array $plan): void
{
$this->errors = [];
$payload = $message->getPayload() ?? [];
@ -68,7 +71,7 @@ class Webhooks extends Action
foreach ($project->getAttribute('webhooks', []) as $webhook) {
if (array_intersect($webhook->getAttribute('events', []), $events)) {
$this->execute($events, $webhookPayload, $webhook, $user, $project, $dbForPlatform, $queueForMails, $queueForStatsUsage);
$this->execute($events, $webhookPayload, $webhook, $user, $project, $dbForPlatform, $queueForMails, $queueForStatsUsage, $plan);
}
}
@ -85,9 +88,10 @@ class Webhooks extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Mail $queueForMails
* @param array $plan
* @return void
*/
private function execute(array $events, string $payload, Document $webhook, Document $user, Document $project, Database $dbForPlatform, Mail $queueForMails, StatsUsage $queueForStatsUsage): void
private function execute(array $events, string $payload, Document $webhook, Document $user, Document $project, Database $dbForPlatform, Mail $queueForMails, StatsUsage $queueForStatsUsage, array $plan): void
{
if ($webhook->getAttribute('enabled') !== true) {
return;
@ -163,7 +167,7 @@ class Webhooks extends Action
if ($attempts >= \intval(System::getEnv('_APP_WEBHOOK_MAX_FAILED_ATTEMPTS', '10'))) {
$webhook->setAttribute('enabled', false);
$this->sendEmailAlert($attempts, $statusCode, $webhook, $project, $dbForPlatform, $queueForMails);
$this->sendEmailAlert($attempts, $statusCode, $webhook, $project, $dbForPlatform, $queueForMails, $plan);
}
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook);
@ -198,9 +202,10 @@ class Webhooks extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Mail $queueForMails
* @param array $plan
* @return void
*/
public function sendEmailAlert(int $attempts, mixed $statusCode, Document $webhook, Document $project, Database $dbForPlatform, Mail $queueForMails): void
public function sendEmailAlert(int $attempts, mixed $statusCode, Document $webhook, Document $project, Database $dbForPlatform, Mail $queueForMails, array $plan): void
{
$memberships = $dbForPlatform->find('memberships', [
Query::equal('teamInternalId', [$project->getAttribute('teamInternalId')]),
@ -225,6 +230,14 @@ class Webhooks extends Action
$template->setParam('{{path}}', "/console/project-$projectId/settings/webhooks/$webhookId");
$template->setParam('{{attempts}}', $attempts);
$template->setParam('{{logoUrl}}', $plan['logoUrl'] ?? APP_EMAIL_LOGO_URL);
$template->setParam('{{accentColor}}', $plan['accentColor'] ?? APP_EMAIL_ACCENT_COLOR);
$template->setParam('{{twitterUrl}}', $plan['twitterUrl'] ?? APP_SOCIAL_TWITTER);
$template->setParam('{{discordUrl}}', $plan['discordUrl'] ?? APP_SOCIAL_DISCORD);
$template->setParam('{{githubUrl}}', $plan['githubUrl'] ?? APP_SOCIAL_GITHUB_APPWRITE);
$template->setParam('{{termsUrl}}', $plan['termsUrl'] ?? APP_EMAIL_TERMS_URL);
$template->setParam('{{privacyUrl}}', $plan['privacyUrl'] ?? APP_EMAIL_PRIVACY_URL);
// TODO: Use setbodyTemplate once #7307 is merged
$subject = 'Webhook deliveries have been paused';
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl');