better emails

This commit is contained in:
Andras Bacsai 2023-09-01 15:52:18 +02:00
parent 76510b8971
commit 3fa53556f4
28 changed files with 374 additions and 139 deletions

View File

@ -0,0 +1,184 @@
<?php
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
use App\Notifications\Test;
use App\Notifications\TransactionalEmails\InvitationLink;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Mail;
use Str;
use function Laravel\Prompts\select;
class TestEmail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'email:test';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a test email to the admin';
/**
* Execute the console command.
*/
private ?MailMessage $mail = null;
public function handle()
{
$email = select(
'Which Email should be sent?',
options: [
'emails-test' => '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())
);
}
}

View File

@ -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)
{

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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}";
}
}

View File

@ -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.";
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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",

2
composer.lock generated
View File

@ -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",

View File

@ -17,7 +17,7 @@
|
*/
'name' => env('APP_NAME', 'Laravel'),
'name' => env('APP_NAME', 'Coolify'),
/*
|--------------------------------------------------------------------------

View File

@ -94,7 +94,7 @@
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'expire' => 10,
'throttle' => 60,
],
],

View File

@ -0,0 +1,6 @@
Thank you.<br>
{{ config('app.name') ?? 'Coolify' }}
{{ Illuminate\Mail\Markdown::parse('---') }}
{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://docs.coollabs.io)') }}

View File

@ -0,0 +1 @@
Hello,

View File

@ -0,0 +1,6 @@
<x-emails.header />
{{ Illuminate\Mail\Markdown::parse($slot) }}
<x-emails.footer />

View File

@ -1,8 +1,11 @@
@if ($pull_request_id !== 0)
Pull Request #{{ $pull_request_id }} of {{ $name }} (<a target="_blank"
href="{{ $fqdn }}">{{ $fqdn }}</a>) deployment failed:
@else
Deployment failed of {{ $name }} (<a target="_blank" href="{{ $fqdn }}">{{ $fqdn }}</a>):
@endif
<x-emails.layout>
@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
<a target="_blank" href="{{ $deployment_url }}">View Deployment Logs</a><br><br>
[View Deployment Logs]({{ $deployment_url }})
</x-emails.layout>

View File

@ -1,8 +1,10 @@
@if ($pull_request_id === 0)
A new version of <a target="_blank" href="{{ $fqdn }}">{{ $fqdn }}</a> is available:
@else
Pull request #{{ $pull_request_id }} of {{ $name }} deployed successfully: <a target="_blank"
href="{{ $fqdn }}">Application Link</a> |
@endif
<a target="_blank" href="{{ $deployment_url }}">View
Deployment Logs</a><br><br>
<x-emails.layout>
@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 }})
</x-emails.layout>

View File

@ -1,2 +1,9 @@
{{ $name }} has been stopped.<br><br>
<a target="_blank" href="{{ $application_url }}">Open in Coolify</a><br><br>
<x-emails.layout>
{{ $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 }}).
</x-emails.layout>

View File

@ -0,0 +1,8 @@
<x-emails.layout>
Database backup for {{ $name }} with frequency of {{ $frequency }} was FAILED.
### Reason
{{ $output }}
</x-emails.layout>

View File

@ -0,0 +1,3 @@
<x-emails.layout>
Database backup for {{ $name }} with frequency of {{ $frequency }} was successful.
</x-emails.layout>

View File

@ -1,13 +1,11 @@
Hello,<br><br>
<x-emails.layout>
You have been invited to "{{ $team }}" on "{{ config('app.name') }}".<br><br>
You have been invited to "{{ $team }}" on "{{ config('app.name') }}".
Please click here to accept the invitation: <a target="_blank" href="{{ $invitation_link }}">Accept Invitation</a><br>
<br>
Please [click here]({{ $invitation_link }}) to accept the invitation.
If you have any questions, please contact the team owner.<br><br>
If it was not you who requested this invitation, please ignore this ema il, or instantly revoke the invitation by
clicking here: <a target="_blank" href="{{ $invitation_link }}/revoke">Revoke Invitation</a><br><br>
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.
</x-emails.layout>

View File

@ -1,6 +1,7 @@
A password reset requested for your email address on "{{ config('app.name') }}".<br><br>
<x-emails.layout>
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: <a target="_blank" href="{{ $url }}">Password
Reset</a><br><br>
Click [here]({{ $url }}) to reset your password.
This password reset link will expire in {{ $count }} minutes.
This link will expire in {{ $count }} minutes.
</x-emails.layout>

View File

@ -1,4 +1,6 @@
Your last invoice has failed to be paid for Coolify Cloud. Please <a href="{{$stripeCustomerPortal}}">update payment details on your Stripe Customer Portal</a>.
<br><br>
Thanks,<br>
Coolify Cloud
<x-emails.layout>
Your last invoice has failed to be paid for Coolify Cloud.
Please update payment details [here]({{$stripeCustomerPortal}}).
</x-emails.layout>

View File

@ -1 +1,3 @@
If you are seeing this, it means that your SMTP settings are correct.
<x-emails.layout>
If you are seeing this, it means that your Email settings are correct.
</x-emails.layout>

View File

@ -1,4 +1,9 @@
Someone added this email to the Coolify Cloud's waitlist.
<br>
<a href="{{ $confirmation_url }}">Click here to confirm</a>! The link will expire in {{config('constants.waitlist.expiration')}} minutes.<br><br>
You have no idea what <a href="https://coolify.io">Coolify Cloud</a> is or this waitlist? <a href="{{ $cancel_url }}">Click here to remove</a> you from the waitlist.
<x-emails.layout>
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.
</x-emails.layout>

View File

@ -1,13 +1,19 @@
You have been invited to join the Coolify Cloud. <a href="{{base_url()}}/login">Login here</a>
<br>
<br>
<x-emails.layout>
You have been invited to join the Coolify Cloud.
[Login here]({{base_url()}}/login)
Here is your initial login information.
<br>
Email: <br>
{{ $email }}
<br><br>
Password:<br>
{{ $password }}
<br><br>
Email:
**{{ $email }}**
Initial Password:
**{{ $password }}**
(You will forced to change it on first login.)
</x-emails.layout>