Merge pull request #7307 from appwrite/feat-smtp-test
Feat smtp test endpoint
This commit is contained in:
commit
9bac4f6728
14 changed files with 317 additions and 82 deletions
|
@ -1,10 +1,9 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=DM+Sans:wght@500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@500;600&display=swap">
|
||||
<style>
|
||||
@media (max-width:500px) {
|
||||
.mobile-full-width {
|
||||
|
@ -21,21 +20,30 @@
|
|||
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;
|
||||
border-spacing: 0 !important;
|
||||
}
|
||||
table,
|
||||
tr,
|
||||
th,
|
||||
td {
|
||||
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;
|
||||
|
@ -47,8 +55,7 @@
|
|||
font-weight: 600;
|
||||
color: #373b4d;
|
||||
}
|
||||
h3,
|
||||
td h3 {
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #373b4d;
|
||||
|
@ -68,7 +75,9 @@
|
|||
border: none;
|
||||
border-top: 1px solid #e8e9f0;
|
||||
}
|
||||
.main a.button {
|
||||
</style>
|
||||
<style>
|
||||
a.button {
|
||||
display: inline-block;
|
||||
background: #fd366e;
|
||||
color: #ffffff;
|
||||
|
@ -85,50 +94,14 @@
|
|||
margin-right: 24px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.main a.button:hover,
|
||||
.main a.button:focus {
|
||||
a.button:hover,
|
||||
a.button:focus {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.main a.button.is-reverse {
|
||||
background: #fff;
|
||||
color: #fd366e;
|
||||
}
|
||||
.fs12 {
|
||||
font-size: 12px;
|
||||
}
|
||||
.ta-right {
|
||||
text-align: right !important;
|
||||
}
|
||||
.ta-left {
|
||||
text-align: left !important;
|
||||
}
|
||||
.ff-dmsans {
|
||||
font-family: "DM Sans", sans-serif !important;
|
||||
}
|
||||
.ff-inter {
|
||||
font-family: "Inter", sans-serif !important;
|
||||
}
|
||||
.w500 {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
.w400 {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
.w600 {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
.tc-accent {
|
||||
color: #da1a5b;
|
||||
}
|
||||
.pt-16 {
|
||||
padding-top: 16px !important;
|
||||
}
|
||||
.pt-32 {
|
||||
padding-top: 32px !important;
|
||||
}
|
||||
.divider {
|
||||
padding-top: 32px;
|
||||
border-bottom: solid 1px #d8d8db;
|
||||
@media only screen and (max-width: 600px) {
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.social-icon {
|
||||
border-radius: 6px;
|
||||
|
@ -140,21 +113,14 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.social-icon > img {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color: #ffffff; margin: 0; padding: 0;>
|
||||
<div class="main" style="max-width: 650px; margin: 0 auto">
|
||||
<body>
|
||||
<div class="main">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
|
@ -176,7 +142,9 @@
|
|||
|
||||
<table style="margin-top: 32px">
|
||||
<tr>
|
||||
<td>{{message}}</td>
|
||||
<td>
|
||||
{{body}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -1,5 +1,74 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@500;600&display=swap">
|
||||
<style>
|
||||
body {
|
||||
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>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
|
@ -64,5 +133,4 @@
|
|||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
12
app/config/locale/templates/email-smtp-test.tpl
Normal file
12
app/config/locale/templates/email-smtp-test.tpl
Normal file
|
@ -0,0 +1,12 @@
|
|||
<p style="margin-block-start: 0;">Hello,</p>
|
||||
|
||||
<p>This email ensures your custom SMTP configuration is working correctly. Please review your sender Information details:</p>
|
||||
|
||||
<p style="margin-block-end: 0;">From: <strong>{{from}}</strong></p>
|
||||
<p style="margin-block-start: 0;">Reply-To: <strong>{{replyTo}}</strong></p>
|
||||
|
||||
<p>If this email was labeled as spam, please check your SMTP server settings to ensure they meet your provider's requirements. Often, missing DNS configurations or security checks mandated by your SMTP provider affect email delivery.</p>
|
||||
<p>If you have trouble with the sender's image, ensure it is set in the <a href="https://gravatar.com/">Gravatar database</a>.</p>
|
||||
|
||||
<p style="margin-block-end: 0;">Best regards,</p>
|
||||
<p style="margin-block-start: 0;">Appwrtite team</p>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
|
@ -1552,12 +1553,12 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
|
|||
|
||||
// CUSTOM SMTP and Templates
|
||||
App::patch('/v1/projects/:projectId/smtp')
|
||||
->desc('Update SMTP configuration')
|
||||
->desc('Update SMTP')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateSmtpConfiguration')
|
||||
->label('sdk.method', 'updateSmtp')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
|
@ -1641,6 +1642,65 @@ App::patch('/v1/projects/:projectId/smtp')
|
|||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::post('/v1/projects/:projectId/smtp/tests')
|
||||
->desc('Create SMTP test')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'createSmtpTest')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('emails', [], new ArrayList(new Email(), 10), 'Array of emails to send test email to. Maximum of 10 emails are allowed.')
|
||||
->param('senderName', App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'), new Text(255, 0), 'Name of the email sender')
|
||||
->param('senderEmail', App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), new Email(), 'Email of the sender')
|
||||
->param('replyTo', '', new Email(), 'Reply to email', true)
|
||||
->param('host', '', new HostName(), 'SMTP server host name')
|
||||
->param('port', 587, new Integer(), 'SMTP server port', true)
|
||||
->param('username', '', new Text(0, 0), 'SMTP server username', true)
|
||||
->param('password', '', new Text(0, 0), 'SMTP server password', true)
|
||||
->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->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 $dbForConsole, Mail $queueForMails) {
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$replyToEmail = !empty($replyTo) ? $replyTo : $senderEmail;
|
||||
|
||||
$subject = 'Custom SMTP email sample';
|
||||
$template = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-smtp-test.tpl');
|
||||
$template
|
||||
->setParam('{{from}}', "{$senderName} ({$senderEmail})")
|
||||
->setParam('{{replyTo}}', "{$senderName} ({$replyToEmail})");
|
||||
|
||||
foreach ($emails as $email) {
|
||||
$queueForMails
|
||||
->setSmtpHost($host)
|
||||
->setSmtpPort($port)
|
||||
->setSmtpUsername($username)
|
||||
->setSmtpPassword($password)
|
||||
->setSmtpSecure($secure)
|
||||
->setSmtpReplyTo($replyTo)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName)
|
||||
->setRecipient($email)
|
||||
->setName('')
|
||||
->setbodyTemplate(__DIR__ . '/../../config/locale/templates/email-base-styled.tpl')
|
||||
->setBody($template->render())
|
||||
->setVariables([])
|
||||
->setSubject($subject)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Get custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
|
|
|
@ -13,6 +13,7 @@ class Mail extends Event
|
|||
protected string $body = '';
|
||||
protected array $smtp = [];
|
||||
protected array $variables = [];
|
||||
protected string $bodyTemplate = '';
|
||||
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
|
@ -115,6 +116,29 @@ class Mail extends Event
|
|||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bodyTemplate for the mail event.
|
||||
*
|
||||
* @param string $bodyTemplate
|
||||
* @return self
|
||||
*/
|
||||
public function setbodyTemplate(string $bodyTemplate): self
|
||||
{
|
||||
$this->bodyTemplate = $bodyTemplate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subject for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getbodyTemplate(): string
|
||||
{
|
||||
return $this->bodyTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP Host
|
||||
*
|
||||
|
@ -327,6 +351,7 @@ class Mail extends Event
|
|||
'recipient' => $this->recipient,
|
||||
'name' => $this->name,
|
||||
'subject' => $this->subject,
|
||||
'bodyTemplate' => $this->bodyTemplate,
|
||||
'body' => $this->body,
|
||||
'smtp' => $this->smtp,
|
||||
'variables' => $this->variables,
|
||||
|
|
|
@ -67,7 +67,15 @@ class Mails extends Action
|
|||
$name = $payload['name'];
|
||||
$body = $payload['body'];
|
||||
|
||||
$bodyTemplate = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
|
||||
$variables['subject'] = $subject;
|
||||
$variables['year'] = date("Y");
|
||||
|
||||
$bodyTemplate = $payload['bodyTemplate'];
|
||||
if (empty($bodyTemplate)) {
|
||||
$bodyTemplate = __DIR__ . '/../../../../app/config/locale/templates/email-base.tpl';
|
||||
}
|
||||
|
||||
$bodyTemplate = Template::fromFile($bodyTemplate);
|
||||
$bodyTemplate->setParam('{{body}}', $body);
|
||||
foreach ($variables as $key => $value) {
|
||||
$bodyTemplate->setParam('{{' . $key . '}}', $value);
|
||||
|
@ -95,7 +103,21 @@ class Mails extends Action
|
|||
$mail->addAddress($recipient, $name);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
$mail->AltBody = \strip_tags($body);
|
||||
|
||||
$mail->AltBody = $body;
|
||||
$mail->AltBody = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $mail->AltBody);
|
||||
$mail->AltBody = \strip_tags($mail->AltBody);
|
||||
$mail->AltBody = \trim($mail->AltBody);
|
||||
|
||||
$replyTo = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
$replyToName = \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
|
||||
if (!empty($smtp)) {
|
||||
$replyTo = !empty($smtp['replyTo']) ? $smtp['replyTo'] : $smtp['senderEmail'];
|
||||
$replyToName = $smtp['senderName'];
|
||||
}
|
||||
|
||||
$mail->addReplyTo($replyTo, $replyToName);
|
||||
|
||||
try {
|
||||
$mail->send();
|
||||
|
@ -130,10 +152,6 @@ class Mails extends Action
|
|||
|
||||
$mail->setFrom($smtp['senderEmail'], $smtp['senderName']);
|
||||
|
||||
if (!empty($smtp['replyTo'])) {
|
||||
$mail->addReplyTo($smtp['replyTo'], $smtp['senderName']);
|
||||
}
|
||||
|
||||
$mail->isHTML();
|
||||
|
||||
return $mail;
|
||||
|
|
|
@ -25,14 +25,19 @@ abstract class Scope extends TestCase
|
|||
$this->client = null;
|
||||
}
|
||||
|
||||
protected function getLastEmail(): array
|
||||
protected function getLastEmail(int $limit = 1): array
|
||||
{
|
||||
sleep(3);
|
||||
|
||||
$emails = json_decode(file_get_contents('http://maildev:1080/email'), true);
|
||||
|
||||
if ($emails && is_array($emails)) {
|
||||
return end($emails);
|
||||
if ($limit === 1) {
|
||||
return end($emails);
|
||||
} else {
|
||||
$lastEmails = array_slice($emails, -1 * $limit);
|
||||
return $lastEmails;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
|
|
|
@ -577,6 +577,85 @@ class ProjectsConsoleClientTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @group smtpAndTemplates
|
||||
* @depends testCreateProject
|
||||
*/
|
||||
public function testCreateProjectSMTPTests($data): array
|
||||
{
|
||||
$id = $data['projectId'];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/smtp/tests', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'emails' => [ 'testuser@appwrite.io', 'testusertwo@appwrite.io' ],
|
||||
'senderEmail' => 'custommailer@appwrite.io',
|
||||
'senderName' => 'Custom Mailer',
|
||||
'replyTo' => 'reply@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
$emails = $this->getLastEmail(2);
|
||||
$this->assertCount(2, $emails);
|
||||
$this->assertEquals('custommailer@appwrite.io', $emails[0]['from'][0]['address']);
|
||||
$this->assertEquals('Custom Mailer', $emails[0]['from'][0]['name']);
|
||||
$this->assertEquals('reply@appwrite.io', $emails[0]['replyTo'][0]['address']);
|
||||
$this->assertEquals('Custom Mailer', $emails[0]['replyTo'][0]['name']);
|
||||
$this->assertEquals('Custom SMTP email sample', $emails[0]['subject']);
|
||||
$this->assertStringContainsStringIgnoringCase('working correctly', $emails[0]['text']);
|
||||
$this->assertStringContainsStringIgnoringCase('working correctly', $emails[0]['html']);
|
||||
$this->assertStringContainsStringIgnoringCase('251 Little Falls Drive', $emails[0]['text']);
|
||||
$this->assertStringContainsStringIgnoringCase('251 Little Falls Drive', $emails[0]['html']);
|
||||
|
||||
$to = [
|
||||
$emails[0]['to'][0]['address'],
|
||||
$emails[1]['to'][0]['address']
|
||||
];
|
||||
\sort($to);
|
||||
|
||||
$this->assertEquals('testuser@appwrite.io', $to[0]);
|
||||
$this->assertEquals('testusertwo@appwrite.io', $to[1]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/smtp/tests', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'emails' => [ 'u1@appwrite.io', 'u2@appwrite.io', 'u3@appwrite.io', 'u4@appwrite.io', 'u5@appwrite.io', 'u6@appwrite.io', 'u7@appwrite.io', 'u8@appwrite.io', 'u9@appwrite.io', 'u10@appwrite.io' ],
|
||||
'senderEmail' => 'custommailer@appwrite.io',
|
||||
'senderName' => 'Custom Mailer',
|
||||
'replyTo' => 'reply@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/smtp/tests', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'emails' => [ 'u1@appwrite.io', 'u2@appwrite.io', 'u3@appwrite.io', 'u4@appwrite.io', 'u5@appwrite.io', 'u6@appwrite.io', 'u7@appwrite.io', 'u8@appwrite.io', 'u9@appwrite.io', 'u10@appwrite.io', 'u11@appwrite.io' ],
|
||||
'senderEmail' => 'custommailer@appwrite.io',
|
||||
'senderName' => 'Custom Mailer',
|
||||
'replyTo' => 'reply@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @group smtpAndTemplates
|
||||
* @depends testUpdateProjectSMTP
|
||||
|
|
Loading…
Reference in a new issue