From ae8bd691065c4775097770300336abb1c9e389aa Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 31 Aug 2023 15:00:59 +0200 Subject: [PATCH] able to use resend for pro+ users --- app/Http/Controllers/ServerController.php | 13 +- app/Http/Livewire/Dashboard.php | 1 + .../Livewire/Notifications/EmailSettings.php | 211 +++++++++++------- app/Http/Livewire/Settings/Email.php | 8 - app/Http/Livewire/Team/Invitations.php | 1 + app/Models/Team.php | 24 ++ .../Application/DeploymentFailed.php | 2 +- app/Notifications/Channels/EmailChannel.php | 27 ++- .../Channels/TransactionalEmailChannel.php | 53 +++-- app/Notifications/Database/BackupFailed.php | 2 +- app/Notifications/Database/BackupSuccess.php | 2 +- app/Notifications/Server/NotReachable.php | 2 +- app/Notifications/Test.php | 2 +- .../TransactionalEmails/ResetPassword.php | 21 +- bootstrap/helpers/shared.php | 56 +++-- bootstrap/helpers/subscriptions.php | 10 +- config/constants.php | 6 +- ...22_071053_add_resend_as_email_to_teams.php | 32 +++ .../emails/waitlist-invitation.blade.php | 15 +- resources/views/layouts/base.blade.php | 2 +- .../notifications/email-settings.blade.php | 117 +++++++--- .../views/livewire/settings/email.blade.php | 8 +- .../views/settings/configuration.blade.php | 4 +- resources/views/team/notifications.blade.php | 2 +- routes/web.php | 5 +- 25 files changed, 407 insertions(+), 219 deletions(-) create mode 100644 database/migrations/2023_08_22_071053_add_resend_as_email_to_teams.php diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index b01a2dd5e..21ca2ba77 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -12,20 +12,21 @@ class ServerController extends Controller public function new_server() { + $privateKeys = PrivateKey::ownedByCurrentTeam()->get(); if (!isCloud()) { return view('server.create', [ 'limit_reached' => false, - 'private_keys' => PrivateKey::ownedByCurrentTeam()->get(), + 'private_keys' => $privateKeys, ]); } - $servers = currentTeam()->servers->count(); - $subscription = currentTeam()?->subscription->type(); - $your_limit = config('constants.limits.server')[strtolower($subscription)]; - $limit_reached = $servers >= $your_limit; + $team = currentTeam(); + $servers = $team->servers->count(); + ['serverLimit' => $serverLimit] = $team->limits; + $limit_reached = $servers >= $serverLimit; return view('server.create', [ 'limit_reached' => $limit_reached, - 'private_keys' => PrivateKey::ownedByCurrentTeam()->get(), + 'private_keys' => $privateKeys, ]); } } diff --git a/app/Http/Livewire/Dashboard.php b/app/Http/Livewire/Dashboard.php index 874e389e0..98c330847 100644 --- a/app/Http/Livewire/Dashboard.php +++ b/app/Http/Livewire/Dashboard.php @@ -6,6 +6,7 @@ use App\Models\S3Storage; use App\Models\Server; use Livewire\Component; +use Log; class Dashboard extends Component { diff --git a/app/Http/Livewire/Notifications/EmailSettings.php b/app/Http/Livewire/Notifications/EmailSettings.php index a2887f989..bf805e5ec 100644 --- a/app/Http/Livewire/Notifications/EmailSettings.php +++ b/app/Http/Livewire/Notifications/EmailSettings.php @@ -6,55 +6,143 @@ use App\Models\Team; use App\Notifications\Test; use Livewire\Component; +use Log; class EmailSettings extends Component { - public Team $model; + public Team $team; public string $emails; + public bool $sharedEmailEnabled = false; protected $rules = [ - 'model.smtp_enabled' => 'nullable|boolean', - 'model.smtp_from_address' => 'required|email', - 'model.smtp_from_name' => 'required', - 'model.smtp_recipients' => 'nullable', - 'model.smtp_host' => 'required', - 'model.smtp_port' => 'required', - 'model.smtp_encryption' => 'nullable', - 'model.smtp_username' => 'nullable', - 'model.smtp_password' => 'nullable', - 'model.smtp_timeout' => 'nullable', - 'model.smtp_notifications_test' => 'nullable|boolean', - 'model.smtp_notifications_deployments' => 'nullable|boolean', - 'model.smtp_notifications_status_changes' => 'nullable|boolean', - 'model.smtp_notifications_database_backups' => 'nullable|boolean', + 'team.smtp_enabled' => 'nullable|boolean', + 'team.smtp_from_address' => 'required|email', + 'team.smtp_from_name' => 'required', + 'team.smtp_recipients' => 'nullable', + 'team.smtp_host' => 'required', + 'team.smtp_port' => 'required', + 'team.smtp_encryption' => 'nullable', + 'team.smtp_username' => 'nullable', + 'team.smtp_password' => 'nullable', + 'team.smtp_timeout' => 'nullable', + 'team.smtp_notifications_test' => 'nullable|boolean', + 'team.smtp_notifications_deployments' => 'nullable|boolean', + 'team.smtp_notifications_status_changes' => 'nullable|boolean', + 'team.smtp_notifications_database_backups' => 'nullable|boolean', + 'team.use_instance_email_settings' => 'boolean', + 'team.resend_enabled' => 'nullable|boolean', + 'team.resend_api_key' => 'nullable', ]; protected $validationAttributes = [ - 'model.smtp_from_address' => 'From Address', - 'model.smtp_from_name' => 'From Name', - 'model.smtp_recipients' => 'Recipients', - 'model.smtp_host' => 'Host', - 'model.smtp_port' => 'Port', - 'model.smtp_encryption' => 'Encryption', - 'model.smtp_username' => 'Username', - 'model.smtp_password' => 'Password', + 'team.smtp_from_address' => 'From Address', + 'team.smtp_from_name' => 'From Name', + 'team.smtp_recipients' => 'Recipients', + 'team.smtp_host' => 'Host', + 'team.smtp_port' => 'Port', + 'team.smtp_encryption' => 'Encryption', + 'team.smtp_username' => 'Username', + 'team.smtp_password' => 'Password', + 'team.smtp_timeout' => 'Timeout', + 'team.resend_enabled' => 'Resend Enabled', + 'team.resend_api_key' => 'Resend API Key', ]; public function mount() { - $this->decrypt(); + ['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits; $this->emails = auth()->user()->email; } - - private function decrypt() + public function submitFromFields() { - if (data_get($this->model, 'smtp_password')) { - try { - $this->model->smtp_password = decrypt($this->model->smtp_password); - } catch (\Exception $e) { + try { + $this->resetErrorBag(); + $this->validate([ + 'team.smtp_from_address' => 'required|email', + 'team.smtp_from_name' => 'required', + ]); + $this->team->save(); + $this->emit('success', 'Settings saved successfully.'); + } catch (\Exception $e) { + return general_error_handler($e, $this); + } + } + public function sendTestNotification() + { + $this->team->notify(new Test($this->emails)); + $this->emit('success', 'Test Email sent successfully.'); + } + public function instantSaveInstance() + { + try { + if (!$this->sharedEmailEnabled) { + throw new \Exception('Not allowed to change settings. Please upgrade your subscription.'); } + $this->team->smtp_enabled = false; + $this->team->resend_enabled = false; + $this->team->save(); + $this->emit('success', 'Settings saved successfully.'); + } catch (\Exception $e) { + return general_error_handler($e, $this); } } + public function instantSaveResend() + { + try { + $this->team->smtp_enabled = false; + $this->submitResend(); + } catch (\Exception $e) { + $this->team->smtp_enabled = false; + return general_error_handler($e, $this); + } + } + public function instantSave() + { + try { + $this->team->resend_enabled = false; + $this->submit(); + } catch (\Exception $e) { + $this->team->smtp_enabled = false; + return general_error_handler($e, $this); + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->validate([ + 'team.smtp_from_address' => 'required|email', + 'team.smtp_from_name' => 'required', + 'team.smtp_host' => 'required', + 'team.smtp_port' => 'required|numeric', + 'team.smtp_encryption' => 'nullable', + 'team.smtp_username' => 'nullable', + 'team.smtp_password' => 'nullable', + 'team.smtp_timeout' => 'nullable', + ]); + $this->team->save(); + $this->emit('success', 'Settings saved successfully.'); + } catch (\Exception $e) { + $this->team->smtp_enabled = false; + return general_error_handler($e, $this); + } + } + public function submitResend() + { + try { + $this->resetErrorBag(); + $this->validate([ + 'team.resend_api_key' => 'required' + ]); + $this->team->save(); + refreshSession(); + $this->emit('success', 'Settings saved successfully.'); + } catch (\Exception $e) { + $this->team->resend_enabled = false; + return general_error_handler($e, $this); + } + } public function copyFromInstanceSettings() { $settings = InstanceSettings::get(); @@ -72,55 +160,22 @@ public function copyFromInstanceSettings() 'smtp_password' => $settings->smtp_password, 'smtp_timeout' => $settings->smtp_timeout, ]); - $this->decrypt(); - if (is_a($team, Team::class)) { - refreshSession(); - } - $this->model = $team; - $this->emit('success', 'Settings saved.'); - } else { - $this->emit('error', 'Instance SMTP settings are not enabled.'); - } - } - - public function sendTestNotification() - { - $this->model->notify(new Test($this->emails)); - $this->emit('success', 'Test Email sent successfully.'); - } - - public function instantSave() - { - try { - $this->submit(); - } catch (\Exception $e) { - $this->model->smtp_enabled = false; - $this->validate(); - } - } - - public function submit() - { - $this->resetErrorBag(); - $this->validate(); - - if ($this->model->smtp_password) { - $this->model->smtp_password = encrypt($this->model->smtp_password); - } else { - $this->model->smtp_password = null; - } - - $this->model->smtp_recipients = str_replace(' ', '', $this->model->smtp_recipients); - $this->saveModel(); - } - - public function saveModel() - { - $this->model->save(); - $this->decrypt(); - if (is_a($this->model, Team::class)) { refreshSession(); + $this->team = $team; + $this->emit('success', 'Settings saved.'); + return; } - $this->emit('success', 'Settings saved.'); + if ($settings->resend_enabled) { + $team = currentTeam(); + $team->update([ + 'resend_enabled' => $settings->resend_enabled, + 'resend_api_key' => $settings->resend_api_key, + ]); + refreshSession(); + $this->team = $team; + $this->emit('success', 'Settings saved.'); + return; + } + $this->emit('error', 'Instance SMTP/Resend settings are not enabled.'); } } diff --git a/app/Http/Livewire/Settings/Email.php b/app/Http/Livewire/Settings/Email.php index 0256b6021..c0e80f020 100644 --- a/app/Http/Livewire/Settings/Email.php +++ b/app/Http/Livewire/Settings/Email.php @@ -54,14 +54,6 @@ public function submitFromFields() { return general_error_handler($e, $this); } } - public function instantSaveResend() - { - try { - $this->submitResend(); - } catch (\Exception $e) { - return general_error_handler($e, $this); - } - } public function submitResend() { try { $this->resetErrorBag(); diff --git a/app/Http/Livewire/Team/Invitations.php b/app/Http/Livewire/Team/Invitations.php index ba6c1e91f..f52701e34 100644 --- a/app/Http/Livewire/Team/Invitations.php +++ b/app/Http/Livewire/Team/Invitations.php @@ -14,6 +14,7 @@ public function deleteInvitation(int $invitation_id) { TeamInvitation::find($invitation_id)->delete(); $this->refreshInvitations(); + $this->emit('success', 'Invitation revoked.'); } public function refreshInvitations() diff --git a/app/Models/Team.php b/app/Models/Team.php index d8d486fec..b75772569 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -4,6 +4,7 @@ use App\Notifications\Channels\SendsDiscord; use App\Notifications\Channels\SendsEmail; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -14,6 +15,8 @@ class Team extends Model implements SendsDiscord, SendsEmail protected $guarded = []; protected $casts = [ 'personal_team' => 'boolean', + 'smtp_password' => 'encrypted', + 'resend_api_key' => 'encrypted', ]; public function routeNotificationForDiscord() @@ -30,6 +33,27 @@ public function getRecepients($notification) } return explode(',', $recipients); } + public function limits(): Attribute + { + return Attribute::make( + get: function () { + if (config('coolify.self_hosted') || $this->id === 0) { + $subscription = 'self-hosted'; + } else { + $subscription = data_get($this, 'subscription'); + if (is_null($subscription)) { + $subscription = 'zero'; + } else { + $subscription = $subscription->type(); + } + } + $serverLimit = config('constants.limits.server')[strtolower($subscription)]; + $sharedEmailEnabled = config('constants.limits.email')[strtolower($subscription)]; + return ['serverLimit' => $serverLimit, 'sharedEmailEnabled' => $sharedEmailEnabled]; + } + + ); + } public function members() { diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index bc7f5dfde..fdd4beabf 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -44,7 +44,7 @@ public function __construct(Application $application, string $deployment_uuid, A public function via(object $notifiable): array { $channels = []; - $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments'); $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments'); diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index afb30d0c9..aaf059149 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -21,7 +21,7 @@ public function send(SendsEmail $notifiable, Notification $notification): void $mailMessage = $notification->toMail($notifiable); if ($this->isResend) { - foreach($recepients as $receipient) { + foreach ($recepients as $receipient) { Mail::send( [], [], @@ -35,7 +35,6 @@ public function send(SendsEmail $notifiable, Notification $notification): void ->html((string)$mailMessage->render()) ); } - } else { Mail::send( [], @@ -50,22 +49,26 @@ public function send(SendsEmail $notifiable, Notification $notification): void ->html((string)$mailMessage->render()) ); } - } private function bootConfigs($notifiable): void { - if (data_get($notifiable, 'resend_enabled')) { - $resendAPIKey = data_get($notifiable, 'resend_api_key'); - if ($resendAPIKey) { - $this->isResend = true; - config()->set('mail.default', 'resend'); - config()->set('resend.api_key', $resendAPIKey); + if (data_get($notifiable, 'use_instance_email_settings')) { + $type = set_transanctional_email_settings(); + if (!$type) { + throw new Exception('No email settings found.'); } + if ($type === 'resend') { + $this->isResend = true; + } + return; + } + if (data_get($notifiable, 'resend_enabled')) { + $this->isResend = true; + config()->set('mail.default', 'resend'); + config()->set('resend.api_key', data_get($notifiable, 'resend_api_key')); } if (data_get($notifiable, 'smtp_enabled')) { - $password = data_get($notifiable, 'smtp_password'); - if ($password) $password = decrypt($password); config()->set('mail.default', 'smtp'); config()->set('mail.mailers.smtp', [ "transport" => "smtp", @@ -73,7 +76,7 @@ private function bootConfigs($notifiable): void "port" => data_get($notifiable, 'smtp_port'), "encryption" => data_get($notifiable, 'smtp_encryption'), "username" => data_get($notifiable, 'smtp_username'), - "password" => $password, + "password" => data_get($notifiable, 'smtp_password'), "timeout" => data_get($notifiable, 'smtp_timeout'), "local_domain" => null, ]); diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php index bf968eb3f..23fe28700 100644 --- a/app/Notifications/Channels/TransactionalEmailChannel.php +++ b/app/Notifications/Channels/TransactionalEmailChannel.php @@ -4,16 +4,20 @@ use App\Models\InstanceSettings; use App\Models\User; +use Exception; use Illuminate\Mail\Message; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Mail; +use Log; class TransactionalEmailChannel { + private bool $isResend = false; public function send(User $notifiable, Notification $notification): void { $settings = InstanceSettings::get(); - if (data_get($settings, 'smtp_enabled') !== true) { + if (!data_get($settings, 'smtp_enabled') && !data_get($settings, 'resend_enabled')) { + Log::info('SMTP/Resend not enabled'); return; } $email = $notifiable->email; @@ -22,22 +26,43 @@ public function send(User $notifiable, Notification $notification): void } $this->bootConfigs(); $mailMessage = $notification->toMail($notifiable); - Mail::send( - [], - [], - fn (Message $message) => $message - ->from( - data_get($settings, 'smtp_from_address'), - data_get($settings, 'smtp_from_name') - ) - ->to($email) - ->subject($mailMessage->subject) - ->html((string)$mailMessage->render()) - ); + if ($this->isResend) { + Mail::send( + [], + [], + fn (Message $message) => $message + ->from( + data_get($settings, 'smtp_from_address'), + data_get($settings, 'smtp_from_name'), + ) + ->to($email) + ->subject($mailMessage->subject) + ->html((string)$mailMessage->render()) + ); + } else { + Mail::send( + [], + [], + fn (Message $message) => $message + ->from( + data_get($settings, 'smtp_from_address'), + data_get($settings, 'smtp_from_name'), + ) + ->bcc($email) + ->subject($mailMessage->subject) + ->html((string)$mailMessage->render()) + ); + } } private function bootConfigs(): void { - set_transanctional_email_settings(); + $type = set_transanctional_email_settings(); + if (!$type) { + throw new Exception('No email settings found.'); + } + if ($type === 'resend') { + $this->isResend = true; + } } } diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index b47f03291..dda0b4884 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -25,7 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database, p public function via(object $notifiable): array { $channels = []; - $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups'); $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups'); diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 82b521019..dad3ee060 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -25,7 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database) public function via(object $notifiable): array { $channels = []; - $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups'); $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups'); diff --git a/app/Notifications/Server/NotReachable.php b/app/Notifications/Server/NotReachable.php index 0b5f71569..bc97d033e 100644 --- a/app/Notifications/Server/NotReachable.php +++ b/app/Notifications/Server/NotReachable.php @@ -23,7 +23,7 @@ public function __construct(public Server $server) public function via(object $notifiable): array { $channels = []; - $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes'); $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes'); diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 4cec28cfd..eb266d0f9 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -20,7 +20,7 @@ public function __construct(public string|null $emails = null) public function via(object $notifiable): array { $channels = []; - $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); if ($isDiscordEnabled && empty($this->emails)) { diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php index e9baa16d1..6844aa705 100644 --- a/app/Notifications/TransactionalEmails/ResetPassword.php +++ b/app/Notifications/TransactionalEmails/ResetPassword.php @@ -31,24 +31,11 @@ public static function toMailUsing($callback) public function via($notifiable) { - if ($this->settings->smtp_enabled) { - $password = data_get($this->settings, 'smtp_password'); - if ($password) $password = decrypt($password); - - config()->set('mail.default', 'smtp'); - config()->set('mail.mailers.smtp', [ - "transport" => "smtp", - "host" => data_get($this->settings, 'smtp_host'), - "port" => data_get($this->settings, 'smtp_port'), - "encryption" => data_get($this->settings, 'smtp_encryption'), - "username" => data_get($this->settings, 'smtp_username'), - "password" => $password, - "timeout" => data_get($this->settings, 'smtp_timeout'), - "local_domain" => null, - ]); - return ['mail']; + $type = set_transanctional_email_settings(); + if (!$type) { + throw new \Exception('No email settings found.'); } - throw new \Exception('SMTP is not enabled'); + return ['mail']; } public function toMail($notifiable) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 8eb557ec1..249f76920 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -73,7 +73,7 @@ function general_error_handler(Throwable | null $err = null, $that = null, $isJs throw new Exception($customErrorMessage ?? "Too many requests. Please try again in {$err->secondsUntilAvailable} seconds."); } else { if ($err->getMessage() === 'This action is unauthorized.') { - return redirect()->route('dashboard')->with('error', $customErrorMessage ?? $err->getMessage()); + return redirect()->route('dashboard')->with('error', $customErrorMessage ?? $err->getMessage()); } throw new Exception($customErrorMessage ?? $err->getMessage()); } @@ -122,10 +122,11 @@ function generateSSHKey() $key = RSA::createKey(); return [ 'private' => $key->toString('PKCS1'), - 'public' => $key->getPublicKey()->toString('OpenSSH',['comment' => 'coolify-generated-ssh-key']) + 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key']) ]; } -function formatPrivateKey(string $privateKey) { +function formatPrivateKey(string $privateKey) +{ $privateKey = trim($privateKey); if (!str_ends_with($privateKey, "\n")) { $privateKey .= "\n"; @@ -140,30 +141,34 @@ function generate_application_name(string $git_repository, string $git_branch): function is_transactional_emails_active(): bool { - return data_get(InstanceSettings::get(), 'smtp_enabled'); + return isEmailEnabled(InstanceSettings::get()); } -function set_transanctional_email_settings(InstanceSettings | null $settings = null): void +function set_transanctional_email_settings(InstanceSettings | null $settings = null): string|null { if (!$settings) { $settings = InstanceSettings::get(); } - $password = data_get($settings, 'smtp_password'); - if (isset($password)) { - $password = decrypt($password); + if (data_get($settings, 'resend_enabled')) { + config()->set('mail.default', 'resend'); + config()->set('resend.api_key', data_get($settings, 'resend_api_key')); + return 'resend'; } - - config()->set('mail.default', 'smtp'); - config()->set('mail.mailers.smtp', [ - "transport" => "smtp", - "host" => data_get($settings, 'smtp_host'), - "port" => data_get($settings, 'smtp_port'), - "encryption" => data_get($settings, 'smtp_encryption'), - "username" => data_get($settings, 'smtp_username'), - "password" => $password, - "timeout" => data_get($settings, 'smtp_timeout'), - "local_domain" => null, - ]); + if (data_get($settings, 'smtp_enabled')) { + config()->set('mail.default', 'smtp'); + config()->set('mail.mailers.smtp', [ + "transport" => "smtp", + "host" => data_get($settings, 'smtp_host'), + "port" => data_get($settings, 'smtp_port'), + "encryption" => data_get($settings, 'smtp_encryption'), + "username" => data_get($settings, 'smtp_username'), + "password" => data_get($settings, 'smtp_password'), + "timeout" => data_get($settings, 'smtp_timeout'), + "local_domain" => null, + ]); + return 'smtp'; + } + return null; } function base_ip(): string @@ -246,7 +251,10 @@ function send_internal_notification(string $message): void function send_user_an_email(MailMessage $mail, string $email): void { $settings = InstanceSettings::get(); - set_transanctional_email_settings($settings); + $type = set_transanctional_email_settings($settings); + if (!$type) { + throw new Exception('No email settings found.'); + } Mail::send( [], [], @@ -259,5 +267,9 @@ function send_user_an_email(MailMessage $mail, string $email): void ->subject($mail->subject) ->html((string) $mail->render()) ); -} +} +function isEmailEnabled($notifiable) +{ + return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings'); +} diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index 4ca32acae..7691ca7d6 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -66,7 +66,6 @@ function isSubscriptionActive() return $subscription->stripe_invoice_paid === true && $subscription->stripe_cancel_at_period_end === false; } return false; - } function isSubscriptionOnGracePeriod() { @@ -92,13 +91,16 @@ function subscriptionProvider() { return config('subscription.provider'); } -function isLemon () { +function isLemon() +{ return config('subscription.provider') === 'lemon'; } -function isStripe() { +function isStripe() +{ return config('subscription.provider') === 'stripe'; } -function isPaddle() { +function isPaddle() +{ return config('subscription.provider') === 'paddle'; } function getStripeCustomerPortalSession(Team $team) diff --git a/config/constants.php b/config/constants.php index 209e9254b..0021f8a5c 100644 --- a/config/constants.php +++ b/config/constants.php @@ -11,11 +11,15 @@ ], 'limits' => [ 'server' => [ + 'zero' => 0, + 'self-hosted' => 999999999999, 'basic' => 1, 'pro' => 10, 'ultimate' => 25, ], - 'smtp' => [ + 'email' => [ + 'zero' => false, + 'self-hosted' => true, 'basic' => false, 'pro' => true, 'ultimate' => true, diff --git a/database/migrations/2023_08_22_071053_add_resend_as_email_to_teams.php b/database/migrations/2023_08_22_071053_add_resend_as_email_to_teams.php new file mode 100644 index 000000000..72be88b2c --- /dev/null +++ b/database/migrations/2023_08_22_071053_add_resend_as_email_to_teams.php @@ -0,0 +1,32 @@ +boolean('resend_enabled')->default(false); + $table->text('resend_api_key')->nullable(); + $table->boolean('use_instance_email_settings')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn('resend_enabled'); + $table->dropColumn('resend_api_key'); + $table->dropColumn('use_instance_email_settings'); + }); + } +}; diff --git a/resources/views/emails/waitlist-invitation.blade.php b/resources/views/emails/waitlist-invitation.blade.php index 7c941d925..4274fba13 100644 --- a/resources/views/emails/waitlist-invitation.blade.php +++ b/resources/views/emails/waitlist-invitation.blade.php @@ -1,14 +1,13 @@ -Congratulations!
-Congratulations!
-
You have been invited to join the Coolify Cloud. Login here

-Credentials: -
-Email: {{ $email }} -
-Password: {{ $password }} +Here is your initial login information.
+Email:
+{{ $email }} +

+Password:
+{{ $password }} +

(You will forced to change it on first login.) diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 9ea6c6e7c..7d352f1b9 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -52,7 +52,7 @@ function changePasswordFieldType(event) { function copyToClipboard(text) { navigator.clipboard.writeText(text); - Livewire.emit('message', 'Copied to clipboard.'); + Livewire.emit('success', 'Copied to clipboard.'); } Livewire.on('reloadWindow', (timeout) => { diff --git a/resources/views/livewire/notifications/email-settings.blade.php b/resources/views/livewire/notifications/email-settings.blade.php index 2de6ec223..1185ad290 100644 --- a/resources/views/livewire/notifications/email-settings.blade.php +++ b/resources/views/livewire/notifications/email-settings.blade.php @@ -16,59 +16,106 @@ Save - @if (isInstanceAdmin()) + @if (isInstanceAdmin() && !$team->use_instance_email_settings) Copy from Instance Settings @endif - @if ($model->smtp_enabled) + @if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings')) Send Test Email @endif - -
- -
-
-
- -
-
- - - -
-
- - - -
-
- - -
+
- @if (data_get($model, 'smtp_enabled')) -

Subscribe to events

+ @if ($this->sharedEmailEnabled) +
+ +
+ @endif + @if (!$team->use_instance_email_settings) +
+ + + + Save + + +
+
+ +
SMTP Server
+
+ +
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+ + Save + +
+
+
+
+
+ +
Resend
+
+ +
+
+
+
+
+
+ +
+
+
+ + Save + +
+
+
+
+
+ @endif + @if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings')) +

Subscribe to events

@if (isDev()) - + @endif

General

-

Applications

- +

Databases

-
@endif diff --git a/resources/views/livewire/settings/email.blade.php b/resources/views/livewire/settings/email.blade.php index 57f66c19f..45ea814c3 100644 --- a/resources/views/livewire/settings/email.blade.php +++ b/resources/views/livewire/settings/email.blade.php @@ -11,9 +11,9 @@
-

Transactional Emails

+

Transactional/Shared Email

-
SMTP settings for password resets, invitations, etc.
+
Email settings for password resets, invitations, shared with Pro+ subscribers etc.
@@ -63,11 +63,11 @@ class="text-white normal-case btn btn-xs no-animation btn-primary">
-
+
Resend
- +
diff --git a/resources/views/settings/configuration.blade.php b/resources/views/settings/configuration.blade.php index 86e24480f..06e5ef9f8 100644 --- a/resources/views/settings/configuration.blade.php +++ b/resources/views/settings/configuration.blade.php @@ -5,9 +5,9 @@ General Backup + @click.prevent="activeTab = 'backup'; window.location.hash = 'backup'" href="#">Instance Backup SMTP + @click.prevent="activeTab = 'smtp'; window.location.hash = 'smtp'" href="#">Transactional/Shared Email
diff --git a/resources/views/team/notifications.blade.php b/resources/views/team/notifications.blade.php index 875102d16..4714c30a7 100644 --- a/resources/views/team/notifications.blade.php +++ b/resources/views/team/notifications.blade.php @@ -12,7 +12,7 @@
- user() ->currentTeam()" />
diff --git a/routes/web.php b/routes/web.php index 1edc599b0..8c4734a82 100644 --- a/routes/web.php +++ b/routes/web.php @@ -28,7 +28,10 @@ Route::post('/forgot-password', function (Request $request) { if (is_transactional_emails_active()) { - set_transanctional_email_settings(); + $type = set_transanctional_email_settings(); + if (!$type) { + return response()->json(['message' => 'Transactional emails are not active'], 400); + } $request->validate([Fortify::email() => 'required|email']); $status = Password::broker(config('fortify.passwords'))->sendResetLink( $request->only(Fortify::email())