diff --git a/app/Http/Controllers/Api/NotificationsController.php b/app/Http/Controllers/Api/NotificationsController.php
index 1cca69a663..fe38dbd1d5 100644
--- a/app/Http/Controllers/Api/NotificationsController.php
+++ b/app/Http/Controllers/Api/NotificationsController.php
@@ -45,6 +45,7 @@ class NotificationsController extends Controller
'use_instance_email_settings' => 'sometimes|boolean',
'deployment_success_email_notifications' => 'sometimes|boolean',
'deployment_failure_email_notifications' => 'sometimes|boolean',
+ 'deployment_commit_details_email_notifications' => 'sometimes|boolean',
'status_change_email_notifications' => 'sometimes|boolean',
'restart_limit_reached_email_notifications' => 'sometimes|boolean',
'backup_success_email_notifications' => 'sometimes|boolean',
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 9287e2030d..9c8a42ce2d 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -5990,9 +5990,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$unsuccessful = $deployments->first(fn (ApplicationDeploymentQueue $deployment) => $deployment->status !== ApplicationDeploymentStatus::FINISHED->value);
+ // Additional servers pull the image and do not clone, so only the main deployment has the commit message.
+ $parentDeployment = ApplicationDeploymentQueue::query()->where('deployment_uuid', $parentDeploymentUuid)->first();
$this->application->environment->project->team?->notify($unsuccessful
- ? new DeploymentFailed($this->application, $unsuccessful->deployment_uuid, $this->preview)
- : new DeploymentSuccess($this->application, $parentDeploymentUuid, $this->preview));
+ ? new DeploymentFailed($this->application, $unsuccessful->deployment_uuid, $this->preview, $parentDeployment)
+ : new DeploymentSuccess($this->application, $parentDeploymentUuid, $this->preview, $parentDeployment));
}
/**
@@ -6001,7 +6003,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function sendDeploymentNotification(string $notificationClass): void
{
$this->application->environment->project->team?->notify(
- new $notificationClass($this->application, $this->deployment_uuid, $this->preview)
+ new $notificationClass($this->application, $this->deployment_uuid, $this->preview, $this->application_deployment_queue)
);
}
diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php
index 0a37c75d16..0913e93507 100644
--- a/app/Livewire/Notifications/Email.php
+++ b/app/Livewire/Notifications/Email.php
@@ -76,6 +76,9 @@ class Email extends Component
#[Validate(['boolean'])]
public bool $deploymentFailureEmailNotifications = true;
+ #[Validate(['boolean'])]
+ public bool $deploymentCommitDetailsEmailNotifications = false;
+
#[Validate(['boolean'])]
public bool $statusChangeEmailNotifications = false;
@@ -156,6 +159,7 @@ class Email extends Component
$this->settings->deployment_success_email_notifications = $this->deploymentSuccessEmailNotifications;
$this->settings->deployment_failure_email_notifications = $this->deploymentFailureEmailNotifications;
+ $this->settings->deployment_commit_details_email_notifications = $this->deploymentCommitDetailsEmailNotifications;
$this->settings->status_change_email_notifications = $this->statusChangeEmailNotifications;
$this->settings->restart_limit_reached_email_notifications = $this->restartLimitReachedEmailNotifications;
$this->settings->backup_success_email_notifications = $this->backupSuccessEmailNotifications;
@@ -197,6 +201,7 @@ class Email extends Component
$this->deploymentSuccessEmailNotifications = $this->settings->deployment_success_email_notifications;
$this->deploymentFailureEmailNotifications = $this->settings->deployment_failure_email_notifications;
+ $this->deploymentCommitDetailsEmailNotifications = $this->settings->deployment_commit_details_email_notifications;
$this->statusChangeEmailNotifications = $this->settings->status_change_email_notifications;
$this->restartLimitReachedEmailNotifications = $this->settings->restart_limit_reached_email_notifications;
$this->backupSuccessEmailNotifications = $this->settings->backup_success_email_notifications;
@@ -231,6 +236,18 @@ class Email extends Component
$this->dispatch('success', 'Email notifications settings updated.');
}
+ public function instantSaveDeploymentCommitDetails()
+ {
+ try {
+ $this->resetErrorBag();
+ $this->saveModel();
+ } catch (\Throwable $e) {
+ $this->deploymentCommitDetailsEmailNotifications = $this->settings->deployment_commit_details_email_notifications;
+
+ return handleError($e, $this);
+ }
+ }
+
public function instantSave(?string $type = null)
{
try {
diff --git a/app/Models/EmailNotificationSettings.php b/app/Models/EmailNotificationSettings.php
index 94d0802d96..98e530ce84 100644
--- a/app/Models/EmailNotificationSettings.php
+++ b/app/Models/EmailNotificationSettings.php
@@ -33,6 +33,7 @@ class EmailNotificationSettings extends Model
'deployment_success_email_notifications',
'deployment_failure_email_notifications',
+ 'deployment_commit_details_email_notifications',
'status_change_email_notifications',
'restart_limit_reached_email_notifications',
'backup_success_email_notifications',
@@ -76,6 +77,7 @@ class EmailNotificationSettings extends Model
'deployment_success_email_notifications' => 'boolean',
'deployment_failure_email_notifications' => 'boolean',
+ 'deployment_commit_details_email_notifications' => 'boolean',
'status_change_email_notifications' => 'boolean',
'restart_limit_reached_email_notifications' => 'boolean',
'backup_success_email_notifications' => 'boolean',
diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php
index 8fff7f03bb..376f120c88 100644
--- a/app/Notifications/Application/DeploymentFailed.php
+++ b/app/Notifications/Application/DeploymentFailed.php
@@ -3,15 +3,19 @@
namespace App\Notifications\Application;
use App\Models\Application;
+use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
+use App\Traits\IncludesDeploymentCommitDetails;
use Illuminate\Notifications\Messages\MailMessage;
class DeploymentFailed extends CustomEmailNotification
{
+ use IncludesDeploymentCommitDetails;
+
public Application $application;
public ?ApplicationPreview $preview = null;
@@ -30,7 +34,7 @@ class DeploymentFailed extends CustomEmailNotification
public ?string $fqdn = null;
- public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
+ public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null, ?ApplicationDeploymentQueue $deployment = null)
{
$this->onQueue('high');
$this->application = $application;
@@ -45,6 +49,7 @@ class DeploymentFailed extends CustomEmailNotification
$this->fqdn = str($this->fqdn)->explode(',')->first();
}
$this->deployment_url = base_url()."/project/{$this->project_uuid}/environment/{$this->environment_uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
+ $this->resolveDeploymentCommitDetails($application, $deployment, $preview);
}
public function via(object $notifiable): array
@@ -52,7 +57,7 @@ class DeploymentFailed extends CustomEmailNotification
return $notifiable->getEnabledChannels('deployment_failure');
}
- public function toMail(): MailMessage
+ public function toMail(?object $notifiable = null): MailMessage
{
$mail = new MailMessage;
$pull_request_id = data_get($this->preview, 'pull_request_id', 0);
@@ -64,6 +69,7 @@ class DeploymentFailed extends CustomEmailNotification
$mail->subject('Coolify: Deployment failed of pull request #'.$this->preview->pull_request_id.' of '.$this->application_name.'.');
}
$mail->view('emails.application-deployment-failed', [
+ 'commit' => $this->deploymentCommitDetailsForMail($notifiable),
'name' => $this->application_name,
'fqdn' => $fqdn,
'deployment_url' => $this->deployment_url,
diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php
index 415df5831d..07c3000fb5 100644
--- a/app/Notifications/Application/DeploymentSuccess.php
+++ b/app/Notifications/Application/DeploymentSuccess.php
@@ -3,15 +3,19 @@
namespace App\Notifications\Application;
use App\Models\Application;
+use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
+use App\Traits\IncludesDeploymentCommitDetails;
use Illuminate\Notifications\Messages\MailMessage;
class DeploymentSuccess extends CustomEmailNotification
{
+ use IncludesDeploymentCommitDetails;
+
public Application $application;
public ?ApplicationPreview $preview = null;
@@ -30,7 +34,7 @@ class DeploymentSuccess extends CustomEmailNotification
public ?string $fqdn;
- public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
+ public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null, ?ApplicationDeploymentQueue $deployment = null)
{
$this->onQueue('high');
$this->application = $application;
@@ -45,6 +49,7 @@ class DeploymentSuccess extends CustomEmailNotification
$this->fqdn = str($this->fqdn)->explode(',')->first();
}
$this->deployment_url = base_url()."/project/{$this->project_uuid}/environment/{$this->environment_uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
+ $this->resolveDeploymentCommitDetails($application, $deployment, $preview);
}
public function via(object $notifiable): array
@@ -52,7 +57,7 @@ class DeploymentSuccess extends CustomEmailNotification
return $notifiable->getEnabledChannels('deployment_success');
}
- public function toMail(): MailMessage
+ public function toMail(?object $notifiable = null): MailMessage
{
$mail = new MailMessage;
$pull_request_id = data_get($this->preview, 'pull_request_id', 0);
@@ -64,6 +69,7 @@ class DeploymentSuccess extends CustomEmailNotification
$mail->subject("Coolify: Pull request #{$pull_request_id} of {$this->application_name} deployed successfully");
}
$mail->view('emails.application-deployment-success', [
+ 'commit' => $this->deploymentCommitDetailsForMail($notifiable),
'name' => $this->application_name,
'fqdn' => $fqdn,
'deployment_url' => $this->deployment_url,
diff --git a/app/Traits/IncludesDeploymentCommitDetails.php b/app/Traits/IncludesDeploymentCommitDetails.php
new file mode 100644
index 0000000000..6248481266
--- /dev/null
+++ b/app/Traits/IncludesDeploymentCommitDetails.php
@@ -0,0 +1,79 @@
+commit);
+ if (preg_match('/^[0-9a-f]{7,64}$/i', $commit) === 1) {
+ $this->commit_sha = $commit;
+ $this->commit_url = $application->gitCommitLink($commit);
+ }
+
+ $message = trim(mb_scrub((string) $deployment->commitMessage(), 'UTF-8'));
+ if ($message !== '') {
+ $this->commit_message = Str::limit($message, 1000);
+ }
+
+ if (is_null($preview) && filled($application->git_branch)) {
+ $this->commit_branch = $application->git_branch;
+ }
+ }
+
+ /** @return array{sha: string|null, short_sha: string|null, url: string|null, branch: string|null, message: string|null}|null */
+ protected function deploymentCommitDetailsForMail(?object $notifiable): ?array
+ {
+ $enabled = (bool) data_get($notifiable, 'emailNotificationSettings.deployment_commit_details_email_notifications', false);
+
+ if (! $enabled || (is_null($this->commit_sha) && is_null($this->commit_message))) {
+ return null;
+ }
+
+ return [
+ 'sha' => $this->commit_sha,
+ 'short_sha' => $this->commit_sha ? substr($this->commit_sha, 0, 7) : null,
+ 'url' => $this->commit_url,
+ 'branch' => $this->commit_branch ? self::escapeMailMarkdown($this->commit_branch) : null,
+ 'message' => $this->commit_message ? self::formatCommitMessageForMail($this->commit_message) : null,
+ ];
+ }
+
+ /** Escape Markdown in user-controlled text; Blade escapes the HTML. */
+ private static function escapeMailMarkdown(string $text): string
+ {
+ return preg_replace('/([\\\\`*_{}\[\]()#+\-.!|~=])/u', '\\\\$1', $text);
+ }
+
+ private static function formatCommitMessageForMail(string $message): string
+ {
+ $paragraphs = preg_split('/\R\s*\R/u', str_replace("\r\n", "\n", $message));
+
+ return collect($paragraphs)
+ ->map(fn (string $paragraph): string => collect(preg_split('/\R/u', $paragraph))
+ ->map(fn (string $line): string => self::escapeMailMarkdown(trim($line)))
+ ->filter(fn (string $line): bool => $line !== '')
+ ->implode("\\\n"))
+ ->filter(fn (string $paragraph): bool => $paragraph !== '')
+ ->implode("\n\n");
+ }
+}
diff --git a/database/migrations/2026_10_18_120000_add_deployment_commit_details_to_email_notification_settings.php b/database/migrations/2026_10_18_120000_add_deployment_commit_details_to_email_notification_settings.php
new file mode 100644
index 0000000000..2f8e613206
--- /dev/null
+++ b/database/migrations/2026_10_18_120000_add_deployment_commit_details_to_email_notification_settings.php
@@ -0,0 +1,26 @@
+boolean('deployment_commit_details_email_notifications')->default(false);
+ });
+ }
+ }
+
+ public function down(): void
+ {
+ if (Schema::hasColumn('email_notification_settings', 'deployment_commit_details_email_notifications')) {
+ Schema::table('email_notification_settings', function (Blueprint $table) {
+ $table->dropColumn('deployment_commit_details_email_notifications');
+ });
+ }
+ }
+};
diff --git a/resources/views/components/emails/deployment-commit.blade.php b/resources/views/components/emails/deployment-commit.blade.php
new file mode 100644
index 0000000000..bf5c23fcf1
--- /dev/null
+++ b/resources/views/components/emails/deployment-commit.blade.php
@@ -0,0 +1,16 @@
+@props(['commit' => null])
+@if ($commit)
+@php
+ $commitLine = null;
+ if ($commit['short_sha']) {
+ $shortSha = e($commit['short_sha']);
+ $commitLine = '**Commit:** '.($commit['url'] ? "[{$shortSha}](".e($commit['url']).')' : $shortSha);
+ if ($commit['branch']) {
+ $commitLine .= ' on '.e($commit['branch']);
+ }
+ }
+ $messageBlock = $commit['message'] ? "**Commit message:**\n\n".e($commit['message']) : null;
+@endphp
+{!! collect([$commitLine, $messageBlock])->filter()->implode("\n\n") !!}
+
+@endif
diff --git a/resources/views/emails/application-deployment-failed.blade.php b/resources/views/emails/application-deployment-failed.blade.php
index a97c650b2a..937c8cdb8e 100644
--- a/resources/views/emails/application-deployment-failed.blade.php
+++ b/resources/views/emails/application-deployment-failed.blade.php
@@ -6,5 +6,6 @@ Failed to deploy a pull request #{{ $pull_request_id }} of {{ $name }} at
[{{ $fqdn }}]({{ $fqdn }}).
@endif
+
',
+ 'click me',
+ '---',
+ 'Co-authored-by: Jane Doe