Add email notification system for new docket entries
- Created NewDocketEntryNotification mailable class - Added professional HTML email template - Updated DocketEntryController to send notifications on entry creation - TESTING MODE: Only sends to chris@deafgain.org - Configured SMTP with Gmail (system@deafgain.org) - Added logging for email delivery tracking - Production code commented out for safety during testing
This commit is contained in:
parent
2393f50b25
commit
9687b9b9be
4 changed files with 267 additions and 9 deletions
16
.env
16
.env
|
|
@ -48,14 +48,14 @@ REDIS_HOST=127.0.0.1
|
|||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=smtp.gmail.com
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=system@deafgain.org
|
||||
MAIL_PASSWORD=ojvlysraxwjriwzy
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS="system@deafgain.org"
|
||||
MAIL_FROM_NAME="MAD Lawsuit Court Docket"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ namespace App\Http\Controllers\Admin;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DocketEntry;
|
||||
use App\Models\Subscription;
|
||||
use App\Mail\NewDocketEntryNotification;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
|
@ -59,8 +63,12 @@ class DocketEntryController extends Controller
|
|||
|
||||
$entry = DocketEntry::create($validated);
|
||||
|
||||
// Send email notifications to active subscribers
|
||||
// TESTING MODE: Only send to chris@deafgain.org
|
||||
$this->sendEmailNotifications($entry);
|
||||
|
||||
return redirect()->route('admin.docket-entries.show', $entry->id)
|
||||
->with('success', 'Docket entry created successfully.');
|
||||
->with('success', 'Docket entry created successfully. Email notifications sent.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -133,4 +141,45 @@ class DocketEntryController extends Controller
|
|||
return redirect()->route('admin.docket-entries.index')
|
||||
->with('success', 'Docket entry deleted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email notifications to subscribers.
|
||||
* TESTING MODE: Only sends to chris@deafgain.org
|
||||
*/
|
||||
private function sendEmailNotifications(DocketEntry $entry): void
|
||||
{
|
||||
try {
|
||||
// TESTING MODE: Only send to chris@deafgain.org
|
||||
// In production, this would query all active subscribers
|
||||
$testEmail = 'chris@deafgain.org';
|
||||
$testToken = 'test-unsubscribe-token'; // Placeholder for testing
|
||||
|
||||
Log::info('Sending email notification for new docket entry', [
|
||||
'entry_id' => $entry->id,
|
||||
'entry_title' => $entry->title,
|
||||
'recipient' => $testEmail,
|
||||
]);
|
||||
|
||||
Mail::to($testEmail)->send(new NewDocketEntryNotification($entry, $testToken));
|
||||
|
||||
Log::info('Email notification sent successfully', [
|
||||
'entry_id' => $entry->id,
|
||||
'recipient' => $testEmail,
|
||||
]);
|
||||
|
||||
// Production code (commented out for testing):
|
||||
// $subscribers = Subscription::where('is_active', true)->get();
|
||||
// foreach ($subscribers as $subscriber) {
|
||||
// Mail::to($subscriber->email)->send(
|
||||
// new NewDocketEntryNotification($entry, $subscriber->unsubscribe_token)
|
||||
// );
|
||||
// }
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to send email notification', [
|
||||
'entry_id' => $entry->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
// Don't throw exception - we don't want email failures to break the entry creation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
56
app/Mail/NewDocketEntryNotification.php
Normal file
56
app/Mail/NewDocketEntryNotification.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\DocketEntry;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class NewDocketEntryNotification extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public DocketEntry $entry,
|
||||
public string $unsubscribeToken
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'New Court Filing: ' . $this->entry->title,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.new-docket-entry',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
153
resources/views/emails/new-docket-entry.blade.php
Normal file
153
resources/views/emails/new-docket-entry.blade.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>New Court Filing Notification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 20px auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1f2937 0%, #374151 100%);
|
||||
color: white;
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.header p {
|
||||
margin: 5px 0 0 0;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.content {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
.filing-info {
|
||||
background-color: #f9fafb;
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.filing-info .label {
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.filing-info .title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.filing-info .date {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.filing-info .summary {
|
||||
color: #4b5563;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
margin: 20px 0;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.cta-button:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f9fafb;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
.footer a {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
}
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: #e5e7eb;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<h1>New Court Filing</h1>
|
||||
<p>Eliza Kragh v. Montana Association of the Deaf</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>A new document has been filed in the case:</p>
|
||||
|
||||
<div class="filing-info">
|
||||
<div class="label">Filing Date</div>
|
||||
<div class="date">{{ $entry->date->format('F j, Y') }}</div>
|
||||
|
||||
<div class="label">Title</div>
|
||||
<div class="title">{{ $entry->title }}</div>
|
||||
|
||||
<div class="label">Summary</div>
|
||||
<div class="summary">{{ $entry->summary }}</div>
|
||||
</div>
|
||||
|
||||
<center>
|
||||
<a href="{{ config('app.url') }}" class="cta-button">View Full Docket</a>
|
||||
</center>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<p style="font-size: 14px; color: #6b7280;">
|
||||
You are receiving this email because you subscribed to notifications for new court filings in this case.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>
|
||||
<strong>Eliza Kragh v. Montana Association of the Deaf</strong><br>
|
||||
Court Docket Notification System
|
||||
</p>
|
||||
<p style="margin-top: 10px;">
|
||||
<a href="{{ config('app.url') }}/unsubscribe/{{ $unsubscribeToken }}">Unsubscribe from notifications</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue