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:
TheMaddax 2025-12-18 10:21:31 -07:00
parent 2393f50b25
commit 9687b9b9be
4 changed files with 267 additions and 9 deletions

16
.env
View file

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

View file

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

View 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 [];
}
}

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