diff --git a/app/Console/Commands/TestEmail.php b/app/Console/Commands/TestEmail.php new file mode 100644 index 000000000..e876b62a0 --- /dev/null +++ b/app/Console/Commands/TestEmail.php @@ -0,0 +1,184 @@ + 'Test', + 'application-deployment-success' => 'Application - Deployment Success', + 'application-deployment-failed' => 'Application - Deployment Failed', + 'application-status-changed' => 'Application - Status Changed', + 'backup-success' => 'Database - Backup Success', + 'backup-failed' => 'Database - Backup Failed', + 'invitation-link' => 'Invitation Link', + 'waitlist-invitation-link' => 'Waitlist Invitation Link', + 'waitlist-confirmation' => 'Waitlist Confirmation', + ], + ); + $type = set_transanctional_email_settings(); + if (!$type) { + throw new Exception('No email settings found.'); + } + $this->mail = new MailMessage(); + $this->mail->subject("Test Email"); + switch ($email) { + case 'emails-test': + $this->mail = (new Test())->toMail(); + break; + case 'application-deployment-success': + $application = Application::all()->first(); + $this->mail = (new DeploymentSuccess($application, 'test'))->toMail(); + $this->sendEmail(); + break; + case 'application-deployment-failed': + $application = Application::all()->first(); + $preview = ApplicationPreview::all()->first(); + if (!$preview) { + $preview = ApplicationPreview::create([ + 'application_id' => $application->id, + 'pull_request_id' => 1, + 'pull_request_html_url' => 'http://example.com', + 'fqdn' => $application->fqdn, + ]); + } + $this->mail = (new DeploymentFailed($application, 'test'))->toMail(); + $this->sendEmail(); + $this->mail = (new DeploymentFailed($application, 'test', $preview))->toMail(); + $this->sendEmail(); + break; + case 'application-status-changed': + $application = Application::all()->first(); + $this->mail = (new StatusChanged($application))->toMail(); + $this->sendEmail(); + break; + case 'backup-failed': + $backup = ScheduledDatabaseBackup::all()->first(); + $db = StandalonePostgresql::all()->first(); + if (!$backup) { + $backup = ScheduledDatabaseBackup::create([ + 'enabled' => true, + 'frequency' => 'daily', + 'save_s3' => false, + 'database_id' => $db->id, + 'database_type' => $db->getMorphClass(), + 'team_id' => 0, + ]); + } + $output = 'Because of an error, the backup of the database ' . $db->name . ' failed.'; + $this->mail = (new BackupFailed($backup, $db, $output))->toMail(); + $this->sendEmail(); + break; + case 'backup-success': + $backup = ScheduledDatabaseBackup::all()->first(); + $db = StandalonePostgresql::all()->first(); + if (!$backup) { + $backup = ScheduledDatabaseBackup::create([ + 'enabled' => true, + 'frequency' => 'daily', + 'save_s3' => false, + 'database_id' => $db->id, + 'database_type' => $db->getMorphClass(), + 'team_id' => 0, + ]); + } + $this->mail = (new BackupSuccess($backup, $db))->toMail(); + $this->sendEmail(); + break; + case 'invitation-link': + $user = User::all()->first(); + $invitation = TeamInvitation::whereEmail($user->email)->first(); + if (!$invitation) { + $invitation = TeamInvitation::create([ + 'uuid' => Str::uuid(), + 'email' => $user->email, + 'team_id' => 1, + 'link' => 'http://example.com', + ]); + } + $this->mail = (new InvitationLink($user))->toMail(); + $this->sendEmail(); + break; + case 'waitlist-invitation-link': + $this->mail = new MailMessage(); + $this->mail->view('emails.waitlist-invitation', [ + 'email' => 'test2@example.com', + 'password' => "supersecretpassword", + ]); + $this->mail->subject('Congratulations! You are invited to join Coolify Cloud.'); + $this->sendEmail(); + break; + case 'waitlist-confirmation': + $this->mail = new MailMessage(); + $this->mail->view( + 'emails.waitlist-confirmation', + [ + 'confirmation_url' => 'http://example.com', + 'cancel_url' => 'http://example.com', + ] + ); + $this->mail->subject('You are on the waitlist!'); + $this->sendEmail(); + break; + } + } + private function sendEmail() + { + Mail::send( + [], + [], + fn (Message $message) => $message + ->from( + 'internal@example.com', + 'Test Email', + ) + ->to('test@example.com') + ->subject($this->mail->subject) + ->html((string)$this->mail->render()) + ); + } +} diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 38ac5127d..13775abae 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -4,15 +4,7 @@ class ApplicationPreview extends BaseModel { - protected $fillable = [ - 'uuid', - 'pull_request_id', - 'pull_request_html_url', - 'pull_request_issue_comment_id', - 'fqdn', - 'status', - 'application_id', - ]; + protected $guarded = []; static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id) { diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index fdd4beabf..b244f4be8 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -18,15 +18,15 @@ class DeploymentFailed extends Notification implements ShouldQueue public Application $application; public string $deployment_uuid; - public ApplicationPreview|null $preview; + public ?ApplicationPreview $preview = null; public string $application_name; - public string|null $deployment_url = null; + public ?string $deployment_url = null; public string $project_uuid; public string $environment_name; - public string|null $fqdn; + public ?string $fqdn = null; - public function __construct(Application $application, string $deployment_uuid, ApplicationPreview|null $preview) + public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null) { $this->application = $application; $this->deployment_uuid = $deployment_uuid; @@ -67,9 +67,8 @@ public function toMail(): MailMessage $mail->subject('❌ Deployment failed of ' . $this->application_name . '.'); } else { $fqdn = $this->preview->fqdn; - $mail->subject('❌ Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' deployment failed.'); + $mail->subject('❌ Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.'); } - $mail->view('emails.application-deployment-failed', [ 'name' => $this->application_name, 'fqdn' => $fqdn, diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index aaf059149..0c042fcfc 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -20,35 +20,33 @@ public function send(SendsEmail $notifiable, Notification $notification): void } $mailMessage = $notification->toMail($notifiable); - if ($this->isResend) { - foreach ($recepients as $receipient) { - Mail::send( - [], - [], - fn (Message $message) => $message - ->from( - data_get($notifiable, 'smtp_from_address'), - data_get($notifiable, 'smtp_from_name'), - ) - ->to($receipient) - ->subject($mailMessage->subject) - ->html((string)$mailMessage->render()) - ); - } - } else { - Mail::send( - [], - [], - fn (Message $message) => $message - ->from( - data_get($notifiable, 'smtp_from_address'), - data_get($notifiable, 'smtp_from_name'), - ) - ->bcc($recepients) - ->subject($mailMessage->subject) - ->html((string)$mailMessage->render()) - ); - } + // if ($this->isResend) { + Mail::send( + [], + [], + fn (Message $message) => $message + ->from( + data_get($notifiable, 'smtp_from_address'), + data_get($notifiable, 'smtp_from_name'), + ) + ->to($recepients) + ->subject($mailMessage->subject) + ->html((string)$mailMessage->render()) + ); + // } else { + // Mail::send( + // [], + // [], + // fn (Message $message) => $message + // ->from( + // data_get($notifiable, 'smtp_from_address'), + // data_get($notifiable, 'smtp_from_name'), + // ) + // ->bcc($recepients) + // ->subject($mailMessage->subject) + // ->html((string)$mailMessage->render()) + // ); + // } } private function bootConfigs($notifiable): void diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php index 23fe28700..aa5541dee 100644 --- a/app/Notifications/Channels/TransactionalEmailChannel.php +++ b/app/Notifications/Channels/TransactionalEmailChannel.php @@ -26,33 +26,33 @@ public function send(User $notifiable, Notification $notification): void } $this->bootConfigs(); $mailMessage = $notification->toMail($notifiable); - 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()) - ); - } + // 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 diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index dda0b4884..79fd9405d 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -14,12 +14,13 @@ class BackupFailed extends Notification implements ShouldQueue { use Queueable; - public string $message = 'Backup FAILED'; - + public string $name; + public string $frequency; public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output) { - $this->message = "❌ Database backup for {$database->name} with frequency of $backup->frequency was FAILED.\n\nReason: $output"; + $this->name = $database->name; + $this->frequency = $backup->frequency; } public function via(object $notifiable): array @@ -36,20 +37,23 @@ public function via(object $notifiable): array if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { $channels[] = DiscordChannel::class; } - ray($channels); return $channels; } public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("❌ Backup FAILED for {$this->database->name}"); - $mail->line($this->message); + $mail->subject("❌ [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); + $mail->view('emails.backup-failed', [ + 'name' => $this->name, + 'frequency' => $this->frequency, + 'output' => $this->output, + ]); return $mail; } public function toDiscord(): string { - return $this->message; + return "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; } } diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index dad3ee060..1b279d632 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -14,12 +14,13 @@ class BackupSuccess extends Notification implements ShouldQueue { use Queueable; - public string $message = 'Backup Success'; - + public string $name; + public string $frequency; public function __construct(ScheduledDatabaseBackup $backup, public $database) { - $this->message = "✅ Database backup for {$database->name} with frequency of $backup->frequency was successful."; + $this->name = $database->name; + $this->frequency = $backup->frequency; } public function via(object $notifiable): array @@ -42,13 +43,16 @@ public function via(object $notifiable): array public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("✅ Backup success for {$this->database->name}"); - $mail->line($this->message); + $mail->subject("✅ Backup successfully done for {$this->database->name}"); + $mail->view('emails.backup-success', [ + 'name' => $this->name, + 'frequency' => $this->frequency, + ]); return $mail; } public function toDiscord(): string { - return $this->message; + return "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful."; } } diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index eb266d0f9..36f0e1053 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -36,7 +36,7 @@ public function via(object $notifiable): array public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("Coolify Test Notification"); + $mail->subject("Test Email"); $mail->view('emails.test'); return $mail; } diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index 409234fd5..96157c7e6 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -20,16 +20,19 @@ public function via(): array return [TransactionalEmailChannel::class]; } - public function toMail(User $user): MailMessage + public function __construct(public User $user) { - $invitation = TeamInvitation::whereEmail($user->email)->first(); + } + public function toMail(): MailMessage + { + $invitation = TeamInvitation::whereEmail($this->user->email)->first(); $invitation_team = Team::find($invitation->team->id); $mail = new MailMessage(); $mail->subject('Invitation for ' . $invitation_team->name); $mail->view('emails.invitation-link', [ 'team' => $invitation_team->name, - 'email' => $user->email, + 'email' => $this->user->email, 'invitation_link' => $invitation->link, ]); return $mail; diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index f5962fc2a..3f3a009bb 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -24,7 +24,7 @@ public function via(): array public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject('Test Notification'); + $mail->subject('Test Email'); $mail->view('emails.test'); return $mail; } diff --git a/composer.json b/composer.json index b4164bade..c8acddbe5 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "laravel/fortify": "^v1.16.0", "laravel/framework": "^v10.7.1", "laravel/horizon": "^5.15", + "laravel/prompts": "^0.1.6", "laravel/sanctum": "^v3.2.1", "laravel/tinker": "^v2.8.1", "laravel/ui": "^4.2", diff --git a/composer.lock b/composer.lock index 3ab2d9fa4..cef0f8b2b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dbb08df7a80c46ce2b9b9fa397ed71c1", + "content-hash": "0603276b60e77cd859fabacdaaf31550", "packages": [ { "name": "aws/aws-crt-php", diff --git a/config/app.php b/config/app.php index 690029fd6..246ad1f99 100644 --- a/config/app.php +++ b/config/app.php @@ -17,7 +17,7 @@ | */ - 'name' => env('APP_NAME', 'Laravel'), + 'name' => env('APP_NAME', 'Coolify'), /* |-------------------------------------------------------------------------- diff --git a/config/auth.php b/config/auth.php index 9548c15de..83c36c0d3 100644 --- a/config/auth.php +++ b/config/auth.php @@ -94,7 +94,7 @@ 'users' => [ 'provider' => 'users', 'table' => 'password_reset_tokens', - 'expire' => 60, + 'expire' => 10, 'throttle' => 60, ], ], diff --git a/resources/views/components/emails/footer.blade.php b/resources/views/components/emails/footer.blade.php new file mode 100644 index 000000000..ae189c21c --- /dev/null +++ b/resources/views/components/emails/footer.blade.php @@ -0,0 +1,6 @@ + +Thank you.
+{{ config('app.name') ?? 'Coolify' }} + +{{ Illuminate\Mail\Markdown::parse('---') }} +{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://docs.coollabs.io)') }} diff --git a/resources/views/components/emails/header.blade.php b/resources/views/components/emails/header.blade.php new file mode 100644 index 000000000..552085f0e --- /dev/null +++ b/resources/views/components/emails/header.blade.php @@ -0,0 +1 @@ +Hello, diff --git a/resources/views/components/emails/layout.blade.php b/resources/views/components/emails/layout.blade.php new file mode 100644 index 000000000..b9ecb355b --- /dev/null +++ b/resources/views/components/emails/layout.blade.php @@ -0,0 +1,6 @@ + +{{ Illuminate\Mail\Markdown::parse($slot) }} + + + + diff --git a/resources/views/emails/application-deployment-failed.blade.php b/resources/views/emails/application-deployment-failed.blade.php index c9a35860d..defe52e4f 100644 --- a/resources/views/emails/application-deployment-failed.blade.php +++ b/resources/views/emails/application-deployment-failed.blade.php @@ -1,8 +1,11 @@ -@if ($pull_request_id !== 0) - Pull Request #{{ $pull_request_id }} of {{ $name }} ({{ $fqdn }}) deployment failed: -@else - Deployment failed of {{ $name }} ({{ $fqdn }}): -@endif + + @if ($pull_request_id === 0) + Failed to deploy a new version of {{ $name }} at [{{ $fqdn }}]({{ $fqdn }}) . + @else + Failed to deploy a pull request #{{ $pull_request_id }} of {{ $name }} at + [{{ $fqdn }}]({{ $fqdn }}). + @endif -View Deployment Logs

+[View Deployment Logs]({{ $deployment_url }}) + +
diff --git a/resources/views/emails/application-deployment-success.blade.php b/resources/views/emails/application-deployment-success.blade.php index ae52aa913..d8426e33c 100644 --- a/resources/views/emails/application-deployment-success.blade.php +++ b/resources/views/emails/application-deployment-success.blade.php @@ -1,8 +1,10 @@ -@if ($pull_request_id === 0) - A new version of {{ $fqdn }} is available: -@else - Pull request #{{ $pull_request_id }} of {{ $name }} deployed successfully: Application Link | -@endif -View - Deployment Logs

+ + @if ($pull_request_id === 0) + A new version of {{ $name }} is available at [{{ $fqdn }}]({{ $fqdn }}) . + @else + Pull request #{{ $pull_request_id }} of {{ $name }} deployed successfully [{{ $fqdn }}]({{ $fqdn }}). + @endif + +[View Deployment Logs]({{ $deployment_url }}) + + diff --git a/resources/views/emails/application-status-changes.blade.php b/resources/views/emails/application-status-changes.blade.php index c2b73ef92..918a4aa55 100644 --- a/resources/views/emails/application-status-changes.blade.php +++ b/resources/views/emails/application-status-changes.blade.php @@ -1,2 +1,9 @@ -{{ $name }} has been stopped.

-Open in Coolify

+ + +{{ $name }} has been stopped. + +If it was your intention to stop this application, you can ignore this email. + +If not, [check what is going on]({{ $application_url }}). + + diff --git a/resources/views/emails/backup-failed.blade.php b/resources/views/emails/backup-failed.blade.php new file mode 100644 index 000000000..712777ef5 --- /dev/null +++ b/resources/views/emails/backup-failed.blade.php @@ -0,0 +1,8 @@ + +Database backup for {{ $name }} with frequency of {{ $frequency }} was FAILED. + +### Reason + +{{ $output }} + + diff --git a/resources/views/emails/backup-success.blade.php b/resources/views/emails/backup-success.blade.php new file mode 100644 index 000000000..0d54a254c --- /dev/null +++ b/resources/views/emails/backup-success.blade.php @@ -0,0 +1,3 @@ + +Database backup for {{ $name }} with frequency of {{ $frequency }} was successful. + diff --git a/resources/views/emails/invitation-link.blade.php b/resources/views/emails/invitation-link.blade.php index cdeccc974..05c36bb8a 100644 --- a/resources/views/emails/invitation-link.blade.php +++ b/resources/views/emails/invitation-link.blade.php @@ -1,13 +1,11 @@ -Hello,

+ -You have been invited to "{{ $team }}" on "{{ config('app.name') }}".

+You have been invited to "{{ $team }}" on "{{ config('app.name') }}". -Please click here to accept the invitation: Accept Invitation
-
+Please [click here]({{ $invitation_link }}) to accept the invitation. If you have any questions, please contact the team owner.

-If it was not you who requested this invitation, please ignore this ema il, or instantly revoke the invitation by -clicking here: Revoke Invitation

+If it was not you who requested this invitation, please ignore this email, or instantly revoke the invitation by clicking [here]({{ $invitation_link }}/revoke). -Thank you. +
diff --git a/resources/views/emails/reset-password.blade.php b/resources/views/emails/reset-password.blade.php index 30c7b9429..a50aa9eca 100644 --- a/resources/views/emails/reset-password.blade.php +++ b/resources/views/emails/reset-password.blade.php @@ -1,6 +1,7 @@ -A password reset requested for your email address on "{{ config('app.name') }}".

+ +A password reset has been requested for this email address on [{{ config('app.name') }}]({{ config('app.url') }}). -Please click the following link to reset your password: Password - Reset

+Click [here]({{ $url }}) to reset your password. -This password reset link will expire in {{ $count }} minutes. +This link will expire in {{ $count }} minutes. +
diff --git a/resources/views/emails/subscription-invoice-failed.blade.php b/resources/views/emails/subscription-invoice-failed.blade.php index 9d04eebd4..93103dc67 100644 --- a/resources/views/emails/subscription-invoice-failed.blade.php +++ b/resources/views/emails/subscription-invoice-failed.blade.php @@ -1,4 +1,6 @@ -Your last invoice has failed to be paid for Coolify Cloud. Please update payment details on your Stripe Customer Portal. -

-Thanks,
-Coolify Cloud + +Your last invoice has failed to be paid for Coolify Cloud. + +Please update payment details [here]({{$stripeCustomerPortal}}). + + diff --git a/resources/views/emails/test.blade.php b/resources/views/emails/test.blade.php index 63b0b45cc..a845efee5 100644 --- a/resources/views/emails/test.blade.php +++ b/resources/views/emails/test.blade.php @@ -1 +1,3 @@ -If you are seeing this, it means that your SMTP settings are correct. + +If you are seeing this, it means that your Email settings are correct. + diff --git a/resources/views/emails/waitlist-confirmation.blade.php b/resources/views/emails/waitlist-confirmation.blade.php index 2d7e3d6da..363f0b82f 100644 --- a/resources/views/emails/waitlist-confirmation.blade.php +++ b/resources/views/emails/waitlist-confirmation.blade.php @@ -1,4 +1,9 @@ -Someone added this email to the Coolify Cloud's waitlist. -
-Click here to confirm! The link will expire in {{config('constants.waitlist.expiration')}} minutes.

-You have no idea what Coolify Cloud is or this waitlist? Click here to remove you from the waitlist. + +Someone added this email to the Coolify Cloud's waitlist. [Click here]({{ $confirmation_url }}) to confirm! + +The link will expire in {{config('constants.waitlist.expiration')}} minutes. + + +You have no idea what [Coolify Cloud](https://coolify.io) is or this waitlist? [Click here]({{ $cancel_url }}) to remove you from the waitlist. + + diff --git a/resources/views/emails/waitlist-invitation.blade.php b/resources/views/emails/waitlist-invitation.blade.php index 4274fba13..89ee8fa52 100644 --- a/resources/views/emails/waitlist-invitation.blade.php +++ b/resources/views/emails/waitlist-invitation.blade.php @@ -1,13 +1,19 @@ -You have been invited to join the Coolify Cloud. Login here -
-
+ +You have been invited to join the Coolify Cloud. + +[Login here]({{base_url()}}/login) + Here is your initial login information. -
-Email:
-{{ $email }} -

-Password:
-{{ $password }} -

+ +Email: + +**{{ $email }}** + +Initial Password: + +**{{ $password }}** + (You will forced to change it on first login.) +
+