mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
feat(email): add a separate notification sender to the instance email settings
Team notifications sent through the instance email settings, the S3 connection error alert, and the channel disabled alert now use the optional notification from address and name. Account mails keep the default sender. Remove the unused emails command and its views.
This commit is contained in:
1 parent
b919af5f7e
commit
a5b4e1d32e
14 files changed
+108
-313
No files matched your search
@@ -1,277 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
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 Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Str;
|
||||
use Mail;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
class Emails extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'emails';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send out test / prod emails';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
private ?MailMessage $mail = null;
|
||||
|
||||
private ?string $email = null;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$type = select(
|
||||
'Which Email should be sent?',
|
||||
options: [
|
||||
'updates' => 'Send Update Email to all users',
|
||||
'emails-test' => 'Test',
|
||||
'database-backup-statuses-daily' => 'Database - Backup Statuses (Daily)',
|
||||
'application-deployment-success-daily' => 'Application - Deployment Success (Daily)',
|
||||
'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',
|
||||
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||
],
|
||||
);
|
||||
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
||||
if (isDev()) {
|
||||
$this->email = 'test@example.com';
|
||||
} else {
|
||||
if (! in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to:');
|
||||
}
|
||||
}
|
||||
set_transanctional_email_settings();
|
||||
|
||||
$this->mail = new MailMessage;
|
||||
$this->mail->subject('Test Email');
|
||||
switch ($type) {
|
||||
case 'updates':
|
||||
$teams = Team::all();
|
||||
if (! $teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.'.PHP_EOL;
|
||||
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email && $member->marketing_emails) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info('Sending to '.count($emails).' emails.');
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->mail = new MailMessage;
|
||||
$this->mail->subject('One-click Services, Docker Compose support');
|
||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||
'token' => encrypt($email),
|
||||
]);
|
||||
$this->mail->view('emails.updates', ['unsubscribeUrl' => $unsubscribeUrl]);
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'emails-test':
|
||||
$this->mail = (new Test)->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-success-daily':
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
if (! $application->hasDeploymentsInLastDays()) {
|
||||
continue;
|
||||
}
|
||||
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||
$this->sendEmail();
|
||||
}
|
||||
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, $backup->database_name ?? 'unknown'))->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->frequency, $db->name))->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 'realusers-before-trial':
|
||||
$this->mail = new MailMessage;
|
||||
$this->mail->view('emails.before-trial-conversion');
|
||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||
if (! $teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.'.PHP_EOL;
|
||||
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info('Sending to '.count($emails).' emails.');
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'realusers-server-lost-connection':
|
||||
$serverId = text('Server Id');
|
||||
$server = Server::find($serverId);
|
||||
if (! $server) {
|
||||
throw new Exception('Server not found');
|
||||
}
|
||||
$admins = [];
|
||||
$members = $server->team->members;
|
||||
foreach ($members as $member) {
|
||||
if ($member->isAdmin()) {
|
||||
$admins[] = $member->email;
|
||||
}
|
||||
}
|
||||
$this->info('Sending to '.count($admins).' admins.');
|
||||
foreach ($admins as $admin) {
|
||||
$this->info($admin);
|
||||
}
|
||||
$this->mail = new MailMessage;
|
||||
$this->mail->view('emails.server-lost-connection', [
|
||||
'name' => $server->name,
|
||||
]);
|
||||
$this->mail->subject('Action required: Server '.$server->name.' lost connection.');
|
||||
foreach ($admins as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function sendEmail(?string $email = null)
|
||||
{
|
||||
if ($email) {
|
||||
$this->email = $email;
|
||||
}
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($this->email)
|
||||
->subject($this->mail->subject)
|
||||
->html((string) $this->mail->render())
|
||||
);
|
||||
$this->info("Email sent to $this->email successfully. 📧");
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ use OpenApi\Attributes as OA;
|
||||
class InstanceEmailSettingsController extends Controller
|
||||
{
|
||||
private const FIELDS = [
|
||||
'smtp_enabled', 'smtp_from_address', 'smtp_from_name', 'smtp_host',
|
||||
'smtp_enabled', 'smtp_from_address', 'smtp_from_name',
|
||||
'notification_from_address', 'notification_from_name', 'smtp_host',
|
||||
'smtp_port', 'smtp_encryption', 'smtp_username', 'smtp_password',
|
||||
'smtp_timeout', 'smtp_ehlo_domain', 'resend_enabled', 'resend_api_key',
|
||||
];
|
||||
@@ -58,6 +59,8 @@ class InstanceEmailSettingsController extends Controller
|
||||
'smtp_enabled' => 'sometimes|boolean',
|
||||
'smtp_from_address' => 'sometimes|nullable|email',
|
||||
'smtp_from_name' => 'sometimes|nullable|string|max:255',
|
||||
'notification_from_address' => 'sometimes|nullable|email',
|
||||
'notification_from_name' => 'sometimes|nullable|string|max:255',
|
||||
'smtp_host' => 'sometimes|nullable|string|max:255',
|
||||
'smtp_port' => 'sometimes|nullable|integer|min:1|max:65535',
|
||||
'smtp_encryption' => 'sometimes|nullable|string|in:starttls,tls,none',
|
||||
|
||||
@@ -30,6 +30,12 @@ class SettingsEmail extends Component
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpFromName = null;
|
||||
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $notificationFromAddress = null;
|
||||
|
||||
#[Validate(['nullable', 'string', 'max:255'])]
|
||||
public ?string $notificationFromName = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $smtpRecipients = null;
|
||||
|
||||
@@ -63,6 +69,8 @@ class SettingsEmail extends Component
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $testEmailAddress = null;
|
||||
|
||||
public bool $testUsesNotificationSender = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (isInstanceAdmin() === false) {
|
||||
@@ -89,6 +97,8 @@ class SettingsEmail extends Component
|
||||
$this->settings->smtp_ehlo_domain = $this->smtpEhloDomain;
|
||||
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||
$this->settings->notification_from_address = $this->notificationFromAddress;
|
||||
$this->settings->notification_from_name = $this->notificationFromName;
|
||||
|
||||
$this->settings->resend_enabled = $this->resendEnabled;
|
||||
$this->settings->resend_api_key = $this->resendApiKey;
|
||||
@@ -104,6 +114,8 @@ class SettingsEmail extends Component
|
||||
$this->smtpEhloDomain = $this->settings->smtp_ehlo_domain;
|
||||
$this->smtpFromAddress = $this->settings->smtp_from_address;
|
||||
$this->smtpFromName = $this->settings->smtp_from_name;
|
||||
$this->notificationFromAddress = $this->settings->notification_from_address;
|
||||
$this->notificationFromName = $this->settings->notification_from_name;
|
||||
|
||||
$this->resendEnabled = $this->settings->resend_enabled;
|
||||
$this->resendApiKey = $this->settings->resend_api_key;
|
||||
@@ -329,7 +341,10 @@ class SettingsEmail extends Component
|
||||
'testEmailAddress' => 'required|email',
|
||||
'smtpFromAddress' => 'required|email',
|
||||
'smtpFromName' => 'required|string',
|
||||
'notificationFromAddress' => $this->testUsesNotificationSender ? 'required|email' : 'nullable|email',
|
||||
'notificationFromName' => 'nullable|string|max:255',
|
||||
], [
|
||||
'notificationFromAddress.required' => 'Notification from address is required to test the notification sender.',
|
||||
'testEmailAddress.required' => 'Test email address is required.',
|
||||
'testEmailAddress.email' => 'Please enter a valid email address.',
|
||||
'smtpFromAddress.required' => 'From Address is required.',
|
||||
@@ -339,13 +354,18 @@ class SettingsEmail extends Component
|
||||
|
||||
$this->settings->smtp_from_address = $this->smtpFromAddress;
|
||||
$this->settings->smtp_from_name = $this->smtpFromName;
|
||||
$this->settings->notification_from_address = $this->notificationFromAddress;
|
||||
$this->settings->notification_from_name = $this->notificationFromName;
|
||||
$this->saveAndAudit();
|
||||
|
||||
$executed = RateLimiter::attempt(
|
||||
'test-email:'.$this->team->id,
|
||||
$perMinute = 0,
|
||||
function () {
|
||||
$this->team?->notifyNow(new Test($this->testEmailAddress));
|
||||
$this->team?->notifyNow(new Test(
|
||||
$this->testEmailAddress,
|
||||
usesNotificationSender: $this->testUsesNotificationSender,
|
||||
));
|
||||
$this->dispatch('success', 'Test Email sent.');
|
||||
},
|
||||
$decaySeconds = 10,
|
||||
|
||||
@@ -28,6 +28,8 @@ class InstanceSettings extends Model
|
||||
'smtp_enabled',
|
||||
'smtp_from_address',
|
||||
'smtp_from_name',
|
||||
'notification_from_address',
|
||||
'notification_from_name',
|
||||
'smtp_recipients',
|
||||
'smtp_host',
|
||||
'smtp_port',
|
||||
@@ -70,6 +72,8 @@ class InstanceSettings extends Model
|
||||
protected $hidden = [
|
||||
'smtp_from_address',
|
||||
'smtp_from_name',
|
||||
'notification_from_address',
|
||||
'notification_from_name',
|
||||
'smtp_recipients',
|
||||
'smtp_host',
|
||||
'smtp_username',
|
||||
@@ -83,6 +87,8 @@ class InstanceSettings extends Model
|
||||
'smtp_enabled' => 'boolean',
|
||||
'smtp_from_address' => 'encrypted',
|
||||
'smtp_from_name' => 'encrypted',
|
||||
'notification_from_address' => 'encrypted',
|
||||
'notification_from_name' => 'encrypted',
|
||||
'smtp_recipients' => 'encrypted',
|
||||
'smtp_host' => 'encrypted',
|
||||
'smtp_port' => 'integer',
|
||||
|
||||
@@ -268,7 +268,7 @@ class S3Storage extends BaseModel
|
||||
// Get admins directly from the pivot relationship for this specific team
|
||||
$users = $team->members()->wherePivotIn('role', ['admin', 'owner'])->get(['users.id', 'users.email']);
|
||||
foreach ($users as $user) {
|
||||
send_user_an_email($mail, $user->email);
|
||||
send_user_an_email($mail, $user->email, asNotification: true);
|
||||
}
|
||||
$this->unusable_email_sent = true;
|
||||
} catch (\Throwable $emailException) {
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Notifications\Channels;
|
||||
|
||||
use App\Exceptions\NonReportableException;
|
||||
use App\Models\EmailNotificationSettings;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Services\Security\SensitiveDataRedactor;
|
||||
use App\Support\SmtpTransportFactory;
|
||||
@@ -42,6 +43,10 @@ class EmailChannel
|
||||
$settings = $notifiable->emailNotificationSettings;
|
||||
}
|
||||
|
||||
// Only the instance settings have a notification sender. A test email can ask for it explicitly.
|
||||
$asNotification = $settings instanceof InstanceSettings
|
||||
&& (data_get($notification, 'usesNotificationSender') ?? ! $isTransactionalEmail);
|
||||
|
||||
$isResendEnabled = $settings->resend_enabled;
|
||||
$isSmtpEnabled = $settings->smtp_enabled;
|
||||
|
||||
@@ -82,7 +87,7 @@ class EmailChannel
|
||||
if ($isResendEnabled) {
|
||||
$resend = Resend::client($settings->resend_api_key);
|
||||
$response = $resend->emails->send([
|
||||
'from' => mail_from_formatted($settings),
|
||||
'from' => mail_from_formatted($settings, $asNotification),
|
||||
'to' => $recipients,
|
||||
'subject' => $mailMessage->subject,
|
||||
'html' => (string) $mailMessage->render(),
|
||||
@@ -102,7 +107,7 @@ class EmailChannel
|
||||
);
|
||||
$mailer = new Mailer($transport);
|
||||
|
||||
$email = mail_from_email(new Email, $settings)
|
||||
$email = mail_from_email(new Email, $settings, $asNotification)
|
||||
->to(...$recipients)
|
||||
->subject($mailMessage->subject)
|
||||
->html((string) $mailMessage->render());
|
||||
|
||||
@@ -10,8 +10,11 @@ class Test extends CustomEmailNotification
|
||||
{
|
||||
public bool $isTestNotification = true;
|
||||
|
||||
public function __construct(public string $emails, public bool $isTransactionalEmail = true)
|
||||
{
|
||||
public function __construct(
|
||||
public string $emails,
|
||||
public bool $isTransactionalEmail = true,
|
||||
public ?bool $usesNotificationSender = null,
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ trait TracksDeliveryFailures
|
||||
|
||||
$admins = $this->team->members()->wherePivotIn('role', ['admin', 'owner'])->get(['users.id', 'users.email']);
|
||||
foreach ($admins as $admin) {
|
||||
send_user_an_email($mail, $admin->email);
|
||||
send_user_an_email($mail, $admin->email, asNotification: true);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Log::warning('Failed to send notification channel disabled email: '.$e->getMessage());
|
||||
|
||||
@@ -16,17 +16,28 @@ function is_transactional_emails_enabled(): bool
|
||||
}
|
||||
|
||||
/**
|
||||
* The sender of an email. With $asNotification, the optional notification sender of the
|
||||
* instance settings replaces the default sender, so alerts do not share its reputation.
|
||||
*
|
||||
* @return array{address: string, name: string}
|
||||
*/
|
||||
function mail_from_identity(object $settings): array
|
||||
function mail_from_identity(object $settings, bool $asNotification = false): array
|
||||
{
|
||||
if (blank($settings->smtp_from_address ?? null)) {
|
||||
throw new InvalidArgumentException('Transactional email sender address is not configured.');
|
||||
if ($asNotification && filled($settings->notification_from_address ?? null)) {
|
||||
$address = (string) $settings->notification_from_address;
|
||||
$name = trim((string) ($settings->notification_from_name ?? ''));
|
||||
} else {
|
||||
if (blank($settings->smtp_from_address ?? null)) {
|
||||
throw new InvalidArgumentException('Transactional email sender address is not configured.');
|
||||
}
|
||||
|
||||
$address = (string) $settings->smtp_from_address;
|
||||
$name = '';
|
||||
}
|
||||
|
||||
$address = (string) $settings->smtp_from_address;
|
||||
|
||||
$name = trim((string) ($settings->smtp_from_name ?? ''));
|
||||
if ($name === '') {
|
||||
$name = trim((string) ($settings->smtp_from_name ?? ''));
|
||||
}
|
||||
|
||||
return [
|
||||
'address' => $address,
|
||||
@@ -34,29 +45,29 @@ function mail_from_identity(object $settings): array
|
||||
];
|
||||
}
|
||||
|
||||
function mail_from_address(object $settings): Address
|
||||
function mail_from_address(object $settings, bool $asNotification = false): Address
|
||||
{
|
||||
$identity = mail_from_identity($settings);
|
||||
$identity = mail_from_identity($settings, $asNotification);
|
||||
|
||||
return new Address($identity['address'], $identity['name']);
|
||||
}
|
||||
|
||||
function mail_from_formatted(object $settings): string
|
||||
function mail_from_formatted(object $settings, bool $asNotification = false): string
|
||||
{
|
||||
return mail_from_address($settings)->toString();
|
||||
return mail_from_address($settings, $asNotification)->toString();
|
||||
}
|
||||
|
||||
function mail_from_email(Email $email, object $settings): Email
|
||||
function mail_from_email(Email $email, object $settings, bool $asNotification = false): Email
|
||||
{
|
||||
$email->from(mail_from_address($settings));
|
||||
$email->from(mail_from_address($settings, $asNotification));
|
||||
prevent_mail_from_header_folding($email, $settings);
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
function mail_from_message(Message $message, object $settings): Message
|
||||
function mail_from_message(Message $message, object $settings, bool $asNotification = false): Message
|
||||
{
|
||||
$identity = mail_from_identity($settings);
|
||||
$identity = mail_from_identity($settings, $asNotification);
|
||||
$message->from($identity['address'], $identity['name']);
|
||||
prevent_mail_from_header_folding($message->getSymfonyMessage(), $settings);
|
||||
|
||||
@@ -81,7 +92,11 @@ function send_internal_notification(string $message): void
|
||||
}
|
||||
}
|
||||
|
||||
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
|
||||
/**
|
||||
* Send an email through the instance email settings. Use $asNotification for alerts, so they
|
||||
* come from the notification sender when one is set.
|
||||
*/
|
||||
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null, bool $asNotification = false): void
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
$type = set_transanctional_email_settings($settings);
|
||||
@@ -92,7 +107,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => mail_from_message($message, $settings)
|
||||
fn (Message $message) => mail_from_message($message, $settings, $asNotification)
|
||||
->to($email)
|
||||
->replyTo($email)
|
||||
->cc($cc)
|
||||
@@ -103,7 +118,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => mail_from_message($message, $settings)
|
||||
fn (Message $message) => mail_from_message($message, $settings, $asNotification)
|
||||
->to($email)
|
||||
->subject($mail->subject)
|
||||
->html((string) $mail->render())
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('instance_settings', function (Blueprint $table) {
|
||||
$table->text('notification_from_address')->nullable();
|
||||
$table->text('notification_from_name')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('instance_settings', function (Blueprint $table) {
|
||||
$table->dropColumn(['notification_from_address', 'notification_from_name']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
<x-emails.layout>
|
||||
We would like to inform you that a {{ config('constants.limits.trial_period') }} days of trial has been added to all subscription plans.
|
||||
|
||||
You can try out Coolify, without payment information for free. If you like it, you can upgrade to a paid plan at any time.
|
||||
|
||||
[Click here](https://app.coolify.io/subscription/new) to start your trial.
|
||||
</x-emails.layout>
|
||||
@@ -1,3 +0,0 @@
|
||||
<x-emails.layout>
|
||||
If you do not like to receive these emails, you can unsubscribe [here]({{ $unsubscribeUrl }}).
|
||||
</x-emails.layout>
|
||||
@@ -13,6 +13,7 @@
|
||||
<form wire:submit.prevent="sendTestEmail" class="application-settings-form flex flex-col gap-4">
|
||||
<x-forms.input wire:model="testEmailAddress" placeholder="test@example.com" :id="$inputId"
|
||||
label="Recipient" required />
|
||||
<x-forms.checkbox id="testUsesNotificationSender" label="Send from the notification sender" />
|
||||
<div class="flex justify-end border-t border-neutral-200 pt-4 dark:border-white/[0.08]">
|
||||
<button type="submit" class="button" @click="modalOpen=false">Send email</button>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{{-- One bar for the whole page. Three stacked bars made Save run
|
||||
submitResend(), which required an API key even when Resend was off. --}}
|
||||
<x-unsaved-bar action="submit"
|
||||
targets="smtpFromName,smtpFromAddress,smtpHost,smtpPort,smtpEncryption,smtpUsername,smtpPassword,smtpTimeout,smtpEhloDomain,resendApiKey" />
|
||||
targets="smtpFromName,smtpFromAddress,notificationFromName,notificationFromAddress,smtpHost,smtpPort,smtpEncryption,smtpUsername,smtpPassword,smtpTimeout,smtpEhloDomain,resendApiKey" />
|
||||
|
||||
<form wire:submit="submit">
|
||||
<x-application.settings-section title="Sender">
|
||||
@@ -20,6 +20,12 @@
|
||||
label="From name" />
|
||||
<x-forms.input required id="smtpFromAddress" helper="Address used for outgoing email."
|
||||
label="From address" />
|
||||
<x-forms.input id="notificationFromName" placeholder="Uses the from name when empty"
|
||||
helper="Name shown in notification emails (deployments, backups, server alerts) to teams that use the instance email settings."
|
||||
label="Notification from name" />
|
||||
<x-forms.input id="notificationFromAddress" placeholder="Uses the from address when empty"
|
||||
helper="Address used for notification emails. Password resets and invitations still use the from address. Use a separate subdomain (for example alerts@notifications.example.com) to keep the reputation of the two senders apart. Your SMTP provider or Resend must allow this address."
|
||||
label="Notification from address" />
|
||||
</div>
|
||||
</x-application.settings-section>
|
||||
</form>
|
||||
|
||||
Reference in new issue
Block a user