feat: add backstage/filament-mails

This commit is contained in:
idevakk
2025-09-28 22:43:41 +05:30
parent 5325eb34cd
commit 122c4d8f89
18 changed files with 619 additions and 2 deletions

View File

@@ -2,6 +2,8 @@
namespace App\Providers\Filament; namespace App\Providers\Filament;
use Backstage\FilamentMails\Facades\FilamentMails;
use Backstage\FilamentMails\FilamentMailsPlugin;
use Boquizo\FilamentLogViewer\FilamentLogViewerPlugin; use Boquizo\FilamentLogViewer\FilamentLogViewerPlugin;
use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\AuthenticateSession;
@@ -60,6 +62,8 @@ class DashPanelProvider extends PanelProvider
->plugins([ ->plugins([
FilamentLoggerPlugin::make(), FilamentLoggerPlugin::make(),
FilamentLogViewerPlugin::make(), FilamentLogViewerPlugin::make(),
]); FilamentMailsPlugin::make(),
])
->routes(fn () => FilamentMails::routes());
} }
} }

View File

@@ -7,6 +7,7 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"backstage/filament-mails": "^3.0",
"filament/filament": "~4.0", "filament/filament": "~4.0",
"gboquizosanchez/filament-log-viewer": "^2.1", "gboquizosanchez/filament-log-viewer": "^2.1",
"jacobtims/filament-logger": "^1.0", "jacobtims/filament-logger": "^1.0",

212
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "bae273ecc05278912300707444956065", "content-hash": "418b6fd827d97cc67411610b6cff80a2",
"packages": [ "packages": [
{ {
"name": "anourvalar/eloquent-serialize", "name": "anourvalar/eloquent-serialize",
@@ -72,6 +72,159 @@
}, },
"time": "2025-07-30T15:45:57+00:00" "time": "2025-07-30T15:45:57+00:00"
}, },
{
"name": "backstage/filament-mails",
"version": "v3.0.4",
"source": {
"type": "git",
"url": "https://github.com/backstagephp/filament-mails.git",
"reference": "24d5d50c5848f8e8d06a5f9f37b891aa9cc5e8b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/backstagephp/filament-mails/zipball/24d5d50c5848f8e8d06a5f9f37b891aa9cc5e8b4",
"reference": "24d5d50c5848f8e8d06a5f9f37b891aa9cc5e8b4",
"shasum": ""
},
"require": {
"backstage/laravel-mails": "^2.0",
"filament/filament": "^4.0",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.15.0"
},
"require-dev": {
"laravel/pint": "^1.16",
"nunomaduro/collision": "^8.8.0",
"orchestra/testbench": "^9.0|^10.0",
"pestphp/pest": "^3.7",
"pestphp/pest-plugin-arch": "^3.1.0",
"pestphp/pest-plugin-laravel": "^3.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"FilamentMails": "Backstage\\FilamentMails\\Facades\\FilamentMails"
},
"providers": [
"Backstage\\FilamentMails\\FilamentMailsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Backstage\\FilamentMails\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Baspa",
"email": "hello@baspa.dev",
"role": "Developer"
}
],
"description": "View logged mails and events in a beautiful Filament UI.",
"homepage": "https://github.com/backstagephp/filament-mails",
"keywords": [
"backstagephp",
"filament-mails",
"laravel"
],
"support": {
"issues": "https://github.com/backstagephp/filament-mails/issues",
"source": "https://github.com/backstagephp/filament-mails"
},
"funding": [
{
"url": "https://github.com/vormkracht10",
"type": "github"
}
],
"time": "2025-09-26T08:39:10+00:00"
},
{
"name": "backstage/laravel-mails",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/backstagephp/laravel-mails.git",
"reference": "b590728f2295be781ea19abd0275783d1598ca5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/backstagephp/laravel-mails/zipball/b590728f2295be781ea19abd0275783d1598ca5c",
"reference": "b590728f2295be781ea19abd0275783d1598ca5c",
"shasum": ""
},
"require": {
"illuminate/contracts": "^10.0 || ^11.0 || ^12.0",
"laravel/helpers": "^1.7.0",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.15.0"
},
"require-dev": {
"larastan/larastan": "^3.0",
"laravel-notification-channels/discord": "^1.6",
"laravel-notification-channels/telegram": "^4.0 || ^5.0 || ^6.0",
"laravel/pint": "^1.17.0",
"laravel/slack-notification-channel": "^2.5 || ^3.3.2",
"nunomaduro/collision": "^7.5.0|^8.4",
"orchestra/testbench": "^8.5.0|^9.4.0",
"pestphp/pest": "^3.0",
"pestphp/pest-plugin-laravel": "^3.0",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Backstage\\Mails\\MailsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Backstage\\Mails\\": "src",
"Backstage\\Mails\\Database\\Factories\\": "database/factories"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark van Eijk",
"email": "mark@vormkracht10.nl",
"role": "Developer"
}
],
"description": "Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.",
"homepage": "https://github.com/backstagephp/laravel-mails",
"keywords": [
"backstagephp",
"laravel",
"laravel-mails"
],
"support": {
"issues": "https://github.com/backstagephp/laravel-mails/issues",
"source": "https://github.com/backstagephp/laravel-mails/tree/v2.1.0"
},
"funding": [
{
"url": "https://github.com/vormkracht10",
"type": "github"
}
],
"time": "2025-07-22T08:08:52+00:00"
},
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
"version": "v3.0.1", "version": "v3.0.1",
@@ -2568,6 +2721,63 @@
}, },
"time": "2025-09-23T15:33:04+00:00" "time": "2025-09-23T15:33:04+00:00"
}, },
{
"name": "laravel/helpers",
"version": "v1.8.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/helpers.git",
"reference": "d0094b4bc4364560c8ee3a9e956596d760d4afab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/helpers/zipball/d0094b4bc4364560c8ee3a9e956596d760d4afab",
"reference": "d0094b4bc4364560c8ee3a9e956596d760d4afab",
"shasum": ""
},
"require": {
"illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
"php": "^7.2.0|^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^7.0|^8.0|^9.0|^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"files": [
"src/helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Dries Vints",
"email": "dries@laravel.com"
}
],
"description": "Provides backwards compatibility for helpers in the latest Laravel release.",
"keywords": [
"helpers",
"laravel"
],
"support": {
"source": "https://github.com/laravel/helpers/tree/v1.8.1"
},
"time": "2025-09-02T15:31:25+00:00"
},
{ {
"name": "laravel/pint", "name": "laravel/pint",
"version": "v1.25.1", "version": "v1.25.1",

17
config/filament-mails.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
use Backstage\FilamentMails\Resources\EventResource;
use Backstage\FilamentMails\Resources\MailResource;
use Backstage\FilamentMails\Resources\SuppressionResource;
return [
'resources' => [
'mail' => MailResource::class,
'event' => EventResource::class,
'suppression' => SuppressionResource::class,
],
'navigation' => [
'group' => null,
],
];

141
config/mails.php Normal file
View File

@@ -0,0 +1,141 @@
<?php
use Backstage\Mails\Models\Mail;
use Backstage\Mails\Models\MailAttachment;
use Backstage\Mails\Models\MailEvent;
return [
// Eloquent model to use for sent emails
'models' => [
'mail' => Mail::class,
'event' => MailEvent::class,
'attachment' => MailAttachment::class,
],
// Table names for saving sent emails and polymorphic relations to database
'database' => [
'tables' => [
'mails' => 'mails',
'attachments' => 'mail_attachments',
'events' => 'mail_events',
'polymorph' => 'mailables',
],
'pruning' => [
'enabled' => true,
'after' => 30, // days
],
],
'headers' => [
'uuid' => 'X-Mails-UUID',
'associate' => 'X-Mails-Associated-Models',
],
'webhooks' => [
'routes' => [
'prefix' => 'webhooks/mails',
],
'queue' => env('MAILS_QUEUE_WEBHOOKS', false),
],
// Logging mails
'logging' => [
// Enable logging of all sent mails to database
'enabled' => env('MAILS_LOGGING_ENABLED', true),
// Specify attributes to log in database
'attributes' => [
'subject',
'from',
'to',
'reply_to',
'cc',
'bcc',
'html',
'text',
],
// Encrypt all attributes saved to database
'encrypted' => env('MAILS_ENCRYPTED', true),
// Track following events using webhooks from email provider
'tracking' => [
'bounces' => true,
'clicks' => true,
'complaints' => true,
'deliveries' => true,
'opens' => true,
'unsubscribes' => true,
],
// Enable saving mail attachments to disk
'attachments' => [
'enabled' => env('MAILS_LOGGING_ATTACHMENTS_ENABLED', true),
'disk' => env('FILESYSTEM_DISK', 'local'),
'root' => 'mails/attachments',
],
],
// Notifications for important mail events
'notifications' => [
'mail' => [
'to' => ['test@example.com'],
],
'discord' => [
// 'to' => ['1234567890'],
],
'slack' => [
// 'to' => ['https://hooks.slack.com/services/...'],
],
'telegram' => [
// 'to' => ['1234567890'],
],
],
'events' => [
'soft_bounced' => [
'notify' => ['mail'],
],
'hard_bounced' => [
'notify' => ['mail'],
],
'bouncerate' => [
'notify' => [],
'retain' => 30, // days
'treshold' => 1, // %
],
'deliveryrate' => [
'treshold' => 99,
],
'complained' => [
//
],
'unsent' => [
//
],
],
];

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create(config('mails.database.tables.mails'), function (Blueprint $table) {
$table->id();
$table->string('uuid')->nullable()->index();
$table->string('mail_class')->nullable()->index();
$table->string('subject')->nullable();
$table->json('from')->nullable();
$table->json('reply_to')->nullable();
$table->json('to')->nullable();
$table->json('cc')->nullable();
$table->json('bcc')->nullable();
$table->text('html')->nullable();
$table->text('text')->nullable();
$table->unsignedBigInteger('opens')->default(0);
$table->unsignedBigInteger('clicks')->default(0);
$table->timestamp('sent_at')->nullable();
$table->timestamp('resent_at')->nullable();
$table->timestamp('accepted_at')->nullable();
$table->timestamp('delivered_at')->nullable();
$table->timestamp('last_opened_at')->nullable();
$table->timestamp('last_clicked_at')->nullable();
$table->timestamp('complained_at')->nullable();
$table->timestamp('soft_bounced_at')->nullable();
$table->timestamp('hard_bounced_at')->nullable();
$table->timestamp('unsubscribed_at')->nullable();
$table->timestamps();
});
}
};

View File

@@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create(config('mails.database.tables.attachments', 'mail_attachments'), function (Blueprint $table) {
$table->id();
$table->foreignIdFor(config('mails.models.mail'))
->constrained()
->cascadeOnDelete();
$table->string('disk');
$table->string('uuid');
$table->string('filename');
$table->string('mime');
$table->boolean('inline', false);
$table->bigInteger('size');
$table->timestamps();
});
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create(config('mails.database.tables.events', 'mail_events'), function (Blueprint $table) {
$table->id();
$table->foreignIdFor(config('mails.models.mail'))
->constrained()
->cascadeOnDelete();
$table->string('type');
$table->string('ip_address')->nullable();
$table->string('hostname')->nullable();
$table->string('platform')->nullable();
$table->string('os')->nullable();
$table->string('browser')->nullable();
$table->string('user_agent')->nullable();
$table->string('city')->nullable();
$table->char('country_code', 2)->nullable();
$table->string('link')->nullable();
$table->string('tag')->nullable();
$table->json('payload')->nullable();
$table->timestamps();
$table->timestamp('occurred_at')->nullable();
});
}
};

View File

@@ -0,0 +1,19 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create(config('mails.database.tables.polymorph'), function (Blueprint $table) {
$table->id();
$table->foreignIdFor(config('mails.models.mail'))
->constrained()
->cascadeOnDelete();
$table->morphs('mailable');
});
}
};

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table(config('mails.database.tables.events', 'mail_events'), function (Blueprint $table): void {
$table->timestamp('unsuppressed_at')
->nullable()
->after('occurred_at');
});
Schema::table(config('mails.database.tables.mails', 'mails'), function (Blueprint $table): void {
$table->string('mailer')
->after('uuid');
$table->string('stream_id')
->nullable()
->after('mailer');
});
}
};

View File

@@ -0,0 +1,17 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table(config('mails.database.tables.mails'), function (Blueprint $table) {
$table->after('clicks', function (Blueprint $table) {
$table->json('tags')->nullable();
});
});
}
};

View File

@@ -0,0 +1,17 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table(config('mails.database.tables.mails', 'mails'), function (Blueprint $table): void {
$table->string('transport')
->nullable()
->after('mailer');
});
}
};

View File

@@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table(config('mails.database.tables.mails'), function (Blueprint $table) {
$table->longText('html')->nullable()->change();
$table->longText('text')->nullable()->change();
});
}
public function down()
{
Schema::table(config('mails.database.tables.mails'), function (Blueprint $table) {
$table->text('html')->nullable()->change();
$table->text('text')->nullable()->change();
});
}
};

View File

@@ -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()
{
Schema::table(config('mails.database.tables.events', 'mail_events'), function (Blueprint $table) {
$table->longText('link')->nullable()->change();
});
}
public function down()
{
Schema::table(config('mails.database.tables.events', 'mail_events'), function (Blueprint $table) {
$table->string('link')->nullable()->change();
});
}
};

View File

View File

@@ -0,0 +1,8 @@
<a type="button"
href="{{ route('filament.' . Filament\Facades\Filament::getCurrentPanel()->getId() . '.mails.attachment.download', [
'tenant' => Filament\Facades\Filament::getTenant(),
'mail' => $getState()->mail_id,
'attachment' => $getState()->id,
'filename' => $getState()->filename,
]) }}"
class="rounded-md bg-white px-3.5 py-2.5 text-sm font-semibold cursor-pointer text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Download</a>

View File

@@ -0,0 +1,5 @@
<div class="prose prose-sm sm:prose lg:prose-lg xl:prose-2xl max-w-full overflow-x-auto">
<pre class="whitespace-pre-wrap break-words">
<code class="language-html">{{ $html }}</code>
</pre>
</div>

View File

@@ -0,0 +1,6 @@
<div class="w-full h-screen">
<iframe
src="{{ route('filament.' . Filament\Facades\Filament::getCurrentPanel()->getId() . '.mails.preview', ['tenant' => Filament\Facades\Filament::getTenant(), 'mail' => $mail->id]) }}"
class="w-full h-full max-w-full" style="width: 100vw; height: 100vh; border: none;">
</iframe>
</div>