desc('Mails worker') ->inject('message') ->inject('project') ->inject('register') ->inject('log') ->callback($this->action(...)); } /** * @var array */ protected array $richTextParams = [ 'b' => '', '/b' => '', ]; /** * @param Message $message * @param Registry $register * @param Log $log * @throws \PHPMailer\PHPMailer\Exception * @return void * @throws Exception */ public function action(Message $message, Document $project, Registry $register, Log $log): void { Runtime::setHookFlags(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_TCP); $payload = $message->getPayload() ?? []; if (empty($payload)) { throw new Exception('Missing payload'); } $smtp = $payload['smtp']; if (empty($smtp) && empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception('Skipped mail processing. No SMTP configuration has been set.'); } $type = empty($smtp) ? 'cloud' : 'smtp'; $log->addTag('type', $type); $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; $hostname = System::getEnv('_APP_CONSOLE_DOMAIN'); $recipient = $payload['recipient']; $subject = $payload['subject']; $variables = $payload['variables']; $variables['host'] = $protocol . '://' . $hostname; $name = $payload['name']; $body = $payload['body']; $preview = $payload['preview'] ?? ''; $variables['subject'] = $subject; $variables['heading'] = $variables['heading'] ?? $subject; $variables['year'] = date("Y"); $attachment = $payload['attachment'] ?? []; $bodyTemplate = $payload['bodyTemplate']; if (empty($bodyTemplate)) { $bodyTemplate = __DIR__ . '/../../../../app/config/locale/templates/email-base.tpl'; } $bodyTemplate = Template::fromFile($bodyTemplate); $bodyTemplate->setParam('{{body}}', $body, escapeHtml: false); foreach ($variables as $key => $value) { // TODO: hotfix for redirect param $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: $key !== 'redirect'); } foreach ($this->richTextParams as $key => $value) { $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: false); } $previewWhitespace = ''; if (!empty($preview)) { $previewTemplate = Template::fromString($preview); foreach ($variables as $key => $value) { $previewTemplate->setParam('{{' . $key . '}}', $value); } // render() will return the subject in

tags, so use strip_tags() to remove them $preview = \strip_tags($previewTemplate->render()); $previewLen = strlen($preview); if ($previewLen < $this->previewMaxLen) { $previewWhitespace = str_repeat($this->whitespaceCodes, $this->previewMaxLen - $previewLen); } } $bodyTemplate->setParam('{{preview}}', $preview); $bodyTemplate->setParam('{{previewWhitespace}}', $previewWhitespace, false); $body = $bodyTemplate->render(); $subjectTemplate = Template::fromString($subject); foreach ($variables as $key => $value) { $subjectTemplate->setParam('{{' . $key . '}}', $value); } // render() will return the subject in

tags, so use strip_tags() to remove them $subject = \strip_tags($subjectTemplate->render()); /** @var PHPMailer $mail */ $mail = empty($smtp) ? $register->get('smtp') : $this->getMailer($smtp); $mail->clearAddresses(); $mail->clearAllRecipients(); $mail->clearReplyTos(); $mail->clearAttachments(); $mail->clearBCCs(); $mail->clearCCs(); $mail->addAddress($recipient, $name); $mail->Subject = $subject; $mail->Body = $body; $mail->AltBody = $body; $mail->AltBody = preg_replace('/]*>(.*?)<\/style>/is', '', $mail->AltBody); $mail->AltBody = \strip_tags($mail->AltBody); $mail->AltBody = \trim($mail->AltBody); $replyTo = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); $replyToName = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server')); $customMailOptions = $payload['customMailOptions'] ?? []; // fallback hierarchy: Custom options > SMTP config > Defaults. if (!empty($customMailOptions['senderEmail']) || !empty($customMailOptions['senderName'])) { $fromEmail = $customMailOptions['senderEmail'] ?? $mail->From; $fromName = $customMailOptions['senderName'] ?? $mail->FromName; $mail->setFrom($fromEmail, $fromName); } if (!empty($customMailOptions['replyToEmail']) || !empty($customMailOptions['replyToName'])) { $replyTo = $customMailOptions['replyToEmail'] ?? $replyTo; $replyToName = $customMailOptions['replyToName'] ?? $replyToName; } elseif (!empty($smtp)) { $replyTo = !empty($smtp['replyTo']) ? $smtp['replyTo'] : ($smtp['senderEmail'] ?? $replyTo); $replyToName = $smtp['senderName'] ?? $replyToName; } $mail->addReplyTo($replyTo, $replyToName); if (!empty($attachment['content'] ?? '')) { $mail->AddStringAttachment( base64_decode($attachment['content']), $attachment['filename'] ?? 'unknown.file', $attachment['encoding'] ?? PHPMailer::ENCODING_BASE64, $attachment['type'] ?? 'plain/text' ); } try { $mail->send(); } catch (\Throwable $error) { if ($type === 'smtp') { throw new Exception('Error sending mail: ' . $error->getMessage(), 401); } throw new Exception('Error sending mail: ' . $error->getMessage(), 500); } } /** * @param array $smtp * @return PHPMailer * @throws \PHPMailer\PHPMailer\Exception */ protected function getMailer(array $smtp): PHPMailer { $mail = new PHPMailer(true); $mail->isSMTP(); $username = $smtp['username']; $password = $smtp['password']; $mail->XMailer = 'Appwrite Mailer'; $mail->Host = $smtp['host']; $mail->Port = $smtp['port']; $mail->SMTPAuth = (!empty($username) && !empty($password)); $mail->Username = $username; $mail->Password = $password; $mail->SMTPSecure = $smtp['secure']; $mail->SMTPAutoTLS = false; $mail->SMTPKeepAlive = true; $mail->CharSet = 'UTF-8'; $mail->Timeout = 10; /* Connection timeout */ $mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */ $mail->setFrom($smtp['senderEmail'], $smtp['senderName']); $mail->isHTML(); return $mail; } }