diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Credentials/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Credentials/Update.php deleted file mode 100644 index 9117554622..0000000000 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Credentials/Update.php +++ /dev/null @@ -1,144 +0,0 @@ -setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/project/smtp/credentials') - ->httpAlias('/v1/projects/:projectId/smtp') - ->desc('Update project SMTP details') - ->groups(['api', 'project']) - ->label('scope', 'project.write') - ->label('event', 'smtp.*.update') - ->label('audits.event', 'project.smtp.update') - ->label('audits.resource', 'project.smtp/{response.$id}') - ->label('sdk', new Method( - namespace: 'project', - group: 'smtp', - name: 'updateSMTPCredentials', - description: <<param('senderName', '', new Text(256), 'Name of the email sender') - ->param('senderEmail', '', 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') - ->param('username', '', new Text(256), 'SMTP server username', true) - ->param('password', '', new Text(256), 'SMTP server password', true) - ->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true) - ->param('enabled', null, new Nullable(new Boolean()), 'Enable custom SMTP service', optional: true, deprecated: true) // Backwards compatibility - ->inject('response') - ->inject('dbForPlatform') - ->inject('project') - ->inject('authorization') - ->callback($this->action(...)); - } - - - public function action( - string $senderName, - string $senderEmail, - string $replyTo, - string $host, - int $port, - string $username, - string $password, - string $secure, - ?bool $enabled, // Backwards compatibility - Response $response, - Database $dbForPlatform, - Document $project, - Authorization $authorization - ): void { - // Backwards compatibility - if (!\is_null($enabled) && $enabled === false) { - $smtp = $project->getAttribute('smtp', []); - - $smtp['enabled'] = $enabled; - - $project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('smtp', $smtp))); - - $response->dynamic($project, Response::MODEL_PROJECT); - - return; - } - - // Validate SMTP settings - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->SMTPAuth = (!empty($username) && !empty($password)); - $mail->Username = $username; - $mail->Password = $password; - $mail->Host = $host; - $mail->Port = $port; - $mail->SMTPSecure = $secure; - $mail->SMTPAutoTLS = false; - $mail->Timeout = 5; - - try { - $valid = $mail->SmtpConnect(); - - if (!$valid) { - throw new \Exception('Connection is not valid.'); - } - } catch (Throwable $error) { - throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, $error->getMessage()); - } - - $smtp = [ - 'enabled' => true, - 'senderName' => $senderName, - 'senderEmail' => $senderEmail, - 'replyTo' => $replyTo, - 'host' => $host, - 'port' => $port, - 'username' => $username, - 'password' => $password, - 'secure' => $secure, - ]; - - $project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('smtp', $smtp))); - - $response->dynamic($project, Response::MODEL_PROJECT); - } -} diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Status/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Status/Update.php deleted file mode 100644 index 0669f6344f..0000000000 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Status/Update.php +++ /dev/null @@ -1,74 +0,0 @@ -setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/project/smtp/status') - ->desc('Update project SMTP status') - ->groups(['api', 'project']) - ->label('scope', 'project.write') - ->label('event', 'smtp.*.update') - ->label('audits.event', 'project.smtp.update') - ->label('audits.resource', 'project.smtp/{response.$id}') - ->label('sdk', new Method( - namespace: 'project', - group: 'smtp', - name: 'updateSMTPStatus', - description: <<param('enabled', null, new Boolean(), 'SMTP status.') - ->inject('response') - ->inject('dbForPlatform') - ->inject('project') - ->inject('authorization') - ->callback($this->action(...)); - } - - public function action( - bool $enabled, - Response $response, - Database $dbForPlatform, - Document $project, - Authorization $authorization - ): void { - $smtp = $project->getAttribute('smtp', []); - - $smtp['enabled'] = $enabled; - - $project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('smtp', $smtp))); - - $response->dynamic($project, Response::MODEL_PROJECT); - } -} diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Update.php new file mode 100644 index 0000000000..3c25750959 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Update.php @@ -0,0 +1,157 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpPath('/v1/project/smtp') + ->httpAlias('/v1/projects/:projectId/smtp') + ->desc('Update project SMTP configuration') + ->groups(['api', 'project']) + ->label('scope', 'project.write') + ->label('event', 'smtp.*.update') + ->label('audits.event', 'project.smtp.update') + ->label('audits.resource', 'project.smtp/{response.$id}') + ->label('sdk', new Method( + namespace: 'project', + group: 'smtp', + name: 'updateSMTP', + description: <<param('host', '', new Nullable(new Hostname()), 'SMTP server hostname (domain)', optional: true) + ->param('port', 587, new Nullable(new Integer()), 'SMTP server port', optional: true) + ->param('username', '', new Nullable(new Text(256)), 'SMTP server username. Leave empty for no authorization.', optional: true) + ->param('password', '', new Nullable(new Text(256)), 'SMTP server password. Leave empty for no authorization. This property is stored securely and cannot be read in future (write-only).', optional: true) + ->param('senderEmail', '', new Nullable(new Email()), 'Email address shown in inbox as the sender of the email.', optional: true) + ->param('senderName', '', new Nullable(new Text(256)), 'Name shown in inbox as the sender of the email.', optional: true) + ->param('replyToEmail', '', new Nullable(new Email()), 'Email used when user replies to the email.', optional: true) + ->param('replyToName', '', new Nullable(new Text(256)), 'Name used when user replies to the email.', optional: true) + ->param('secure', '', new Nullable(new WhiteList(['tls', 'ssl'], true)), 'Configures if communication with SMTP server is encrypted. Allowed values are: tls, ssl. Leave empty for no encryption.', optional: true) + ->param('enabled', null, new Nullable(new Boolean()), 'Enable or disable custom SMTP. Custom SMTP is useful for branding purposes, but also allows use of custom email templates.', optional: true) + ->inject('response') + ->inject('dbForPlatform') + ->inject('project') + ->inject('authorization') + ->callback($this->action(...)); + } + + + public function action( + ?string $host, + ?int $port, + ?string $username, + ?string $password, + ?string $senderEmail, + ?string $senderName, + ?string $replyToEmail, + ?string $replyToName, + ?string $secure, + ?bool $enabled, + Response $response, + Database $dbForPlatform, + Document $project, + Authorization $authorization + ): void { + // Fetch current configuration + $smtp = $project->getAttribute('smtp', []); + + // Apply changes + $keys = ['host', 'port', 'username', 'password', 'senderEmail', 'senderName', 'replyToEmail', 'replyToName', 'secure', 'enabled']; + foreach ($keys as $key) { + if (!\is_null(${$key})) { + $smtp[$key] = ${$key}; + } + } + + // Ensure required fields are set + $requiredKeys = ['host', 'port', 'senderEmail']; + foreach ($requiredKeys as $key) { + if (empty($smtp[$key])) { + throw new \Exception('"' . $key . '" is required. Please provide a value.'); + } + } + + // Validate SMTP credentials + if($smtp['enabled'] === true) { + $mail = new PHPMailer(true); + $mail->isSMTP(); + + $mail->Host = $smtp['host'] ?? ''; + $mail->Port = $smtp['port'] ?? ''; + $mail->SMTPSecure = $smtp['secure'] ?? ''; + $mail->setFrom($smtp['senderEmail'], $smtp['senderName'] ?? ''); + + if(!empty($smtp['username'] ?? '')) { + $mail->SMTPAuth = true; + $mail->Username = $smtp['username']; + $mail->Password = $smtp['password'] ?? ''; + } + + if(!empty($smtp['replyToEmail'] ?? '')) { + $mail->addReplyTo($smtp['replyToEmail'], $smtp['replyToName'] ?? ''); + } + + $mail->SMTPAutoTLS = false; + $mail->Timeout = 5; + + try { + $valid = $mail->SmtpConnect(); + + if (!$valid) { + throw new \Exception('Connection is not valid.'); + } + } catch (Throwable $error) { + throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, $error->getMessage()); + } + } + + // Save configuration + $updates = new Document([ + 'smtp' => $smtp, + ]); + + $project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $updates)); + + $response->dynamic($project, Response::MODEL_PROJECT); + } +} diff --git a/src/Appwrite/Platform/Modules/Project/Services/Http.php b/src/Appwrite/Platform/Modules/Project/Services/Http.php index 4fea33220b..ac048c6022 100644 --- a/src/Appwrite/Platform/Modules/Project/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Project/Services/Http.php @@ -25,7 +25,6 @@ use Appwrite\Platform\Modules\Project\Http\Project\Platforms\XList as ListPlatfo use Appwrite\Platform\Modules\Project\Http\Project\Protocols\Status\Update as UpdateProjectProtocolStatus; use Appwrite\Platform\Modules\Project\Http\Project\Services\Status\Update as UpdateProjectServiceStatus; use Appwrite\Platform\Modules\Project\Http\Project\SMTP\Credentials\Update as UpdateSMTPCredentials; -use Appwrite\Platform\Modules\Project\Http\Project\SMTP\Status\Update as UpdateSMTPStatus; use Appwrite\Platform\Modules\Project\Http\Project\SMTP\Tests\Create as CreateSMTPTest; use Appwrite\Platform\Modules\Project\Http\Project\Variables\Create as CreateVariable; use Appwrite\Platform\Modules\Project\Http\Project\Variables\Delete as DeleteVariable; @@ -50,7 +49,6 @@ class Http extends Service // SMTP $this->addAction(UpdateSMTPCredentials::getName(), new UpdateSMTPCredentials()); - $this->addAction(UpdateSMTPStatus::getName(), new UpdateSMTPStatus()); $this->addAction(CreateSMTPTest::getName(), new CreateSMTPTest()); // Variables diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 4cb038fc37..e1909c4785 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -247,7 +247,13 @@ class Project extends Model 'default' => '', 'example' => 'john@appwrite.io', ]) - ->addRule('smtpReplyTo', [ + ->addRule('smtpReplyToName', [ + 'type' => self::TYPE_STRING, + 'description' => 'SMTP reply to name', + 'default' => '', + 'example' => 'Support Team', + ]) + ->addRule('smtpReplyToEmail', [ 'type' => self::TYPE_STRING, 'description' => 'SMTP reply to email', 'default' => '', @@ -271,12 +277,15 @@ class Project extends Model 'default' => '', 'example' => 'emailuser', ]) + /* + We intentionally do not expose SMTP password - it's write-only property. ->addRule('smtpPassword', [ 'type' => self::TYPE_STRING, 'description' => 'SMTP server password', 'default' => '', 'example' => 'securepassword', ]) + */ ->addRule('smtpSecure', [ 'type' => self::TYPE_STRING, 'description' => 'SMTP server secure protocol', @@ -409,7 +418,8 @@ class Project extends Model $document->setAttribute('smtpEnabled', $smtp['enabled'] ?? false); $document->setAttribute('smtpSenderEmail', $smtp['senderEmail'] ?? ''); $document->setAttribute('smtpSenderName', $smtp['senderName'] ?? ''); - $document->setAttribute('smtpReplyTo', $smtp['replyTo'] ?? ''); + $document->setAttribute('smtpReplyToEmail', $smtp['replyToEmail'] ?? ''); + $document->setAttribute('smtpReplyToName', $smtp['replyToName'] ?? ''); $document->setAttribute('smtpHost', $smtp['host'] ?? ''); $document->setAttribute('smtpPort', $smtp['port'] ?? ''); $document->setAttribute('smtpUsername', $smtp['username'] ?? '');