From 06ba160e4ab123ecb2d6391a0e17edda91cb969b Mon Sep 17 00:00:00 2001 From: Ravi Gulhane <> Date: Mon, 18 May 2026 11:54:25 +0530 Subject: [PATCH] =Email Changes --- laravel/.env.example | 4 + laravel/app/Console/Commands/SendEmail.php | 40 ++ laravel/app/Console/Commands/SendReminder.php | 150 +++----- laravel/app/Console/Commands/Sendwhatsapp.php | 179 ++++----- laravel/app/Console/Kernel.php | 1 + .../app/Http/Controllers/BotController.php | 100 ++++- .../Http/Controllers/MicrositeController.php | 145 +++++++- .../Http/Controllers/SessionController.php | 128 ++++++- laravel/app/Mail/SessionMessage.php | 55 +++ laravel/app/Models/EmailSend.php | 22 ++ laravel/app/Models/Session.php | 14 + .../Services/SessionCommunicationService.php | 346 ++++++++++++++++++ ..._000001_add_session_communication_mode.php | 44 +++ laravel/docs/LAMBDA_SENDNOTIFICATION.md | 20 + .../views/email/session-message.blade.php | 36 ++ .../resources/views/road/success.blade.php | 124 ++++++- laravel/routes/web.php | 2 + 17 files changed, 1163 insertions(+), 247 deletions(-) create mode 100644 laravel/app/Console/Commands/SendEmail.php create mode 100644 laravel/app/Mail/SessionMessage.php create mode 100644 laravel/app/Models/EmailSend.php create mode 100644 laravel/app/Services/SessionCommunicationService.php create mode 100644 laravel/database/migrations/2026_05_18_000001_add_session_communication_mode.php create mode 100644 laravel/docs/LAMBDA_SENDNOTIFICATION.md create mode 100644 laravel/resources/views/email/session-message.blade.php diff --git a/laravel/.env.example b/laravel/.env.example index c3ed2a9..7a0ec9d 100755 --- a/laravel/.env.example +++ b/laravel/.env.example @@ -34,6 +34,10 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" +CREST_MAIL_FROM= +CREST_MAIL_FROM_NAME=CREST +INTERAKT_API_AUTH= +LAMBDA_ACCESS_TOKEN= AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= diff --git a/laravel/app/Console/Commands/SendEmail.php b/laravel/app/Console/Commands/SendEmail.php new file mode 100644 index 0000000..83e5af8 --- /dev/null +++ b/laravel/app/Console/Commands/SendEmail.php @@ -0,0 +1,40 @@ +where('is_sent', 0)->limit(30)->get(); + + foreach ($sends as $send) { + try { + Mail::to($send->to)->send(new SessionMessage($send->subject, $send->body_html)); + DB::table('email_send')->where('id', $send->id)->update([ + 'is_sent' => 1, + 'response' => 'sent', + 'updated_at' => now(), + ]); + } catch (\Throwable $e) { + DB::table('email_send')->where('id', $send->id)->update([ + 'is_sent' => 1, + 'response' => $e->getMessage(), + 'status' => 0, + 'updated_at' => now(), + ]); + } + } + + return 0; + } +} diff --git a/laravel/app/Console/Commands/SendReminder.php b/laravel/app/Console/Commands/SendReminder.php index 18b8bc0..5098201 100644 --- a/laravel/app/Console/Commands/SendReminder.php +++ b/laravel/app/Console/Commands/SendReminder.php @@ -2,123 +2,91 @@ namespace App\Console\Commands; +use App\Http\Requests\LambdaRequest; +use App\Services\SessionCommunicationService; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; -use App\Http\Requests\LambdaRequest; class SendReminder extends Command { - /** - * The name and signature of the console command. - * - * @var string - */ protected $signature = 'reminder:send'; - /** - * The console command description. - * - * @var string - */ - protected $description = 'Command description'; + protected $description = 'Queue session reminder messages'; - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() + protected $comm; + + public function __construct(SessionCommunicationService $comm) { parent::__construct(); + $this->comm = $comm; } - /** - * Execute the console command. - * - * @return int - */ public function handle() { - $sends = DB::table('message_reminder as ms')->leftjoin('message_template as t', 'ms.template','=','t.id')->where('ms.status', 'active')->select("ms.*", "t.template_code")->get(); - print_r($sends); - $sessions = DB::table('sessions')->where('status', 'active')->select("id", DB::raw("DATEDIFF(start_date, now()) AS days"), "number", "code", "type", "start_date")->get(); + $sends = DB::table('message_reminder as ms') + ->leftJoin('message_template as t', 'ms.template', '=', 't.id') + ->where('ms.status', 'active') + ->select('ms.*', 't.template_code', 't.template_body', 't.title as template_title') + ->get(); - if($sends->count() > 0) { - foreach($sends as $send) { - foreach($sessions as $session) { - print_r([date("H:i", strtotime($send->time)), date("H:i", strtotime("now")+19800 )]); - // && date("H:i", strtotime($send->time)) == date("H:i", strtotime("now")+19800 ) - if(($session->id == 0 || $session->id == $send->session_id) && $session->days == $send->days_before && date("H:i", strtotime($send->time)) == date("H:i", strtotime("now")+19800 ) ) { + $sessions = DB::table('sessions') + ->where('status', 'active') + ->select( + 'id', + DB::raw('DATEDIFF(start_date, now()) AS days'), + 'number', + 'code', + 'type', + 'start_date', + 'venue', + 'communication_mode' + ) + ->get(); + + if ($sends->count() > 0) { + foreach ($sends as $send) { + foreach ($sessions as $session) { + if ( + ($session->id == 0 || $session->id == $send->session_id) + && $session->days == $send->days_before + && date('H:i', strtotime($send->time)) == date('H:i', strtotime('now') + 19800) + ) { $searchparam = ['session_code' => $session->code]; - if($send->audience == 'attended'){ + if ($send->audience == 'attended') { $searchparam['present'] = 'yes'; } - $SESSION_NAME = $session->number; - $SESSION_DATE = date("d M, Y", strtotime($session->start_date) ); - $SESSION_TYPE = strtoupper($session->type); - $SESSION_BROCHURE = 'https://road.xltr.in/brochure/'.$session->code; - $SESSION_CODE = $session->code; - - $response = LambdaRequest::getrequest('contact', 'contact/get_all', ['source'=>'dynamodb', 'search' => json_encode($searchparam), 'fields' => json_encode(['session_code']) ], env('LAMBDA_ACCESS_TOKEN', 'default_value') ); - if( sizeof($response->data->items) > 0 ) { - foreach($response->data->items as $item) { - $RECIPIENT_NAME = $item->name; - $CERT_PARAM = $item->phone.'_'.$send->id; - - if($send->template_code == 'crest_thankyou_and_feedback') { - $SESSION_CODE = $SESSION_CODE . '_' . $item->id; - } + $response = LambdaRequest::getrequest( + 'contact', + 'contact/get_all', + [ + 'source' => 'dynamodb', + 'search' => json_encode($searchparam), + 'fields' => json_encode(['session_code', 'email', 'name', 'phone', 'id']), + ], + env('LAMBDA_ACCESS_TOKEN', 'default_value') + ); - $header_params = []; - $body_params = []; - $button_params = []; + if (sizeof($response->data->items) > 0) { + $config = [ + 'id' => $send->id, + 'template_code' => $send->template_code, + 'template_body' => $send->template_body, + 'template_title' => $send->template_title, + 'header_params' => $send->header_params, + 'body_params' => $send->body_params, + 'button_params' => $send->button_params, + ]; - if($send->header_params != '') { - $header_params = explode('||', $send->header_params); - foreach($header_params as &$var) { - if (isset($$var) && $var != '') { - $var = $$var; - } - } - } - - if($send->body_params != '') { - $body_params = explode('||', $send->body_params); - foreach($body_params as &$var) { - if (isset($$var) && $$var != '') { - $var = $$var; - } - } - } - - if($send->button_params != '') { - $button_params = explode('||', $send->button_params); - foreach($button_params as &$var) { - if (isset($$var) && $$var != '') { - $var = $$var; - } - } - } - - $template = ["name" => $send->template_code, "languageCode" => "en"]; - if(sizeof($header_params)>0) $template["headerValues"] = $header_params; - if(sizeof($body_params)>0) $template["bodyValues"] = $body_params; - if(sizeof($button_params)>0) $template["buttonValues"] = (object)["0" => $button_params]; - - $params = ["countryCode" => "+91", - "phoneNumber" => $item->phone, - "callbackData" => $item->phone.'_'.$item->id, - "type" => "Template", - "template" => $template - ]; - DB::table('raphiwa_send')->insert(['to' => $item->phone, 'text' => json_encode($params) , 'contact_id' => $item->id, 'session_id' => $send->session_id]); + foreach ($response->data->items as $item) { + $this->comm->dispatchForContact($session, $item, $config); } } - } } } } + + return 0; } } diff --git a/laravel/app/Console/Commands/Sendwhatsapp.php b/laravel/app/Console/Commands/Sendwhatsapp.php index 9698b41..0b9ae8d 100644 --- a/laravel/app/Console/Commands/Sendwhatsapp.php +++ b/laravel/app/Console/Commands/Sendwhatsapp.php @@ -2,143 +2,118 @@ namespace App\Console\Commands; +use App\Http\Requests\LambdaRequest; +use App\Services\SessionCommunicationService; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; -use App\Http\Requests\LambdaRequest; use Illuminate\Support\Facades\Http; -use Log; class Sendwhatsapp extends Command { - /** - * The name and signature of the console command. - * - * @var string - */ protected $signature = 'whatsapp:send'; - /** - * The console command description. - * - * @var string - */ - protected $description = 'Command description'; + protected $description = 'Send queued WhatsApp messages and expand active message_send campaigns'; - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() + protected $comm; + + public function __construct(SessionCommunicationService $comm) { parent::__construct(); + $this->comm = $comm; } - /** - * Execute the console command. - * - * @return int - */ - - public function handle() { - //Log::debug('Called'); - $client = new \GuzzleHttp\Client(); + $auth = env('INTERAKT_API_AUTH', 'Basic S1BnVWoxRGZUcWpTYVl2OG9ZN3lEbkk2YjlXTzUtZXhvOWpMQ3RIN3ZJVTo='); + $sends = DB::table('raphiwa_send')->where('is_sent', 0)->limit(30)->get(); - if($sends->count() > 0) { - foreach($sends as $send) { - //print_r(json_decode($send->text)); - $resp = Http::withHeaders(['Authorization' => 'Basic S1BnVWoxRGZUcWpTYVl2OG9ZN3lEbkk2YjlXTzUtZXhvOWpMQ3RIN3ZJVTo='])->post('https://api.interakt.ai/v1/public/message/', (array)json_decode($send->text)); - //$res = $client->get('https://panel.rapiwha.com/send_message.php', ['query' => ['apikey' => 'Z4NXQUTBGV3P7D7W8OHX', 'number' => $send->to,'text' => $send->text], 'timeout' => 10]); - $resp = $resp->getBody(); - $respj = json_decode($resp); + if ($sends->count() > 0) { + foreach ($sends as $send) { + $resp = Http::withHeaders(['Authorization' => $auth]) + ->post('https://api.interakt.ai/v1/public/message/', (array) json_decode($send->text)); + $respj = json_decode($resp->getBody()); print_r($respj); - if($respj->result) { - DB::table('raphiwa_send')->where('id', $send->id)->update(['is_sent' => 1, 'response' => $resp, 'updated_at' => date("Y-m-d H:i:s", strtotime("now") ) ]); + if ($respj->result) { + DB::table('raphiwa_send')->where('id', $send->id)->update([ + 'is_sent' => 1, + 'response' => $resp->getBody(), + 'updated_at' => now(), + ]); } else { - DB::table('raphiwa_send')->where('id', $send->id)->update(['is_sent' => 1, 'response' => $resp, 'updated_at' => date("Y-m-d H:i:s", strtotime("now") ), 'status' => 0 ]); + DB::table('raphiwa_send')->where('id', $send->id)->update([ + 'is_sent' => 1, + 'response' => $resp->getBody(), + 'updated_at' => now(), + 'status' => 0, + ]); } } } else { - $sends = DB::table('message_send as ms')->leftjoin('sessions as s', 'ms.session_id','=','s.id')->leftjoin('message_template as t', 'ms.template','=','t.id')->where('ms.status', 'active')->select("ms.id", "ms.session_id", "s.code", "s.type", "s.number", "s.start_date", "s.venue", "t.template_code", "t.template_body", "ms.header_params", "ms.body_params", "ms.button_params", "ms.audience")->limit(5)->get(); - if($sends->count() > 0) { - foreach($sends as $send) { + $sends = DB::table('message_send as ms') + ->leftJoin('sessions as s', 'ms.session_id', '=', 's.id') + ->leftJoin('message_template as t', 'ms.template', '=', 't.id') + ->where('ms.status', 'active') + ->select( + 'ms.id', + 'ms.session_id', + 's.code', + 's.type', + 's.number', + 's.start_date', + 's.venue', + 's.communication_mode', + 't.template_code', + 't.template_body', + 't.title as template_title', + 'ms.header_params', + 'ms.body_params', + 'ms.button_params', + 'ms.audience' + ) + ->limit(5) + ->get(); + + if ($sends->count() > 0) { + foreach ($sends as $send) { print_r($send); - $body = $send->template_body; - //$variables = ['RECIPIENT_NAME', 'SESSION_NAME', 'SESSION_DATE', 'Dr. Salvi', 'SESSION_TYPE', 'SESSION_BROCHURE']; - $SESSION_NAME = $send->number; - $SESSION_DATE = date("d M, Y", strtotime($send->start_date) ); - $SESSION_TYPE = strtoupper($send->type); - $SESSION_BROCHURE = 'https://road.xltr.in/brochure/'.$send->code; - $SESSION_CODE = $send->code; - - $searchparam = ['session_code' => $send->code]; - if($send->audience == 'attended'){ + if ($send->audience == 'attended') { $searchparam['present'] = 'yes'; } - $response = LambdaRequest::getrequest('contact', 'contact/get_all', ['source'=>'dynamodb', 'search' => json_encode($searchparam), 'fields' => json_encode(['session_code']) ], env('LAMBDA_ACCESS_TOKEN', 'default_value') ); - if( sizeof($response->data->items) > 0 ) { - foreach($response->data->items as $item) { - $RECIPIENT_NAME = $item->name; - $CERT_PARAM = $item->phone.'_'.$send->id; - - if($send->template_code == 'crest_thankyou_and_feedback' || $send->template_code == 'crest_feedback_new' || $send->template_code == 'post_impact_assessment' || $send->template_code == 'pre_impact_assessment') { - $SESSION_CODE = $send->code . '_' . $item->id; - } + $response = LambdaRequest::getrequest( + 'contact', + 'contact/get_all', + [ + 'source' => 'dynamodb', + 'search' => json_encode($searchparam), + 'fields' => json_encode(['session_code', 'email', 'name', 'phone', 'id']), + ], + env('LAMBDA_ACCESS_TOKEN', 'default_value') + ); - $header_params = []; - $body_params = []; - $button_params = []; + if (sizeof($response->data->items) > 0) { + $config = [ + 'id' => $send->id, + 'template_code' => $send->template_code, + 'template_body' => $send->template_body, + 'template_title' => $send->template_title, + 'header_params' => $send->header_params, + 'body_params' => $send->body_params, + 'button_params' => $send->button_params, + ]; - if($send->header_params != '') { - $header_params = explode('||', $send->header_params); - foreach($header_params as &$var) { - if (isset($$var) && $var != '') { - $var = $$var; - } - } - } - - if($send->body_params != '') { - $body_params = explode('||', $send->body_params); - foreach($body_params as &$var) { - if (isset($$var) && $$var != '') { - $var = $$var; - } - } - } - - if($send->button_params != '') { - $button_params = explode('||', $send->button_params); - foreach($button_params as &$var) { - if (isset($$var) && $$var != '') { - $var = $$var; - } - } - } - - $template = ["name" => $send->template_code, "languageCode" => "en"]; - if(sizeof($header_params)>0) $template["headerValues"] = $header_params; - if(sizeof($body_params)>0) $template["bodyValues"] = $body_params; - if(sizeof($button_params)>0) $template["buttonValues"] = (object)["0" => $button_params]; - - $params = ["countryCode" => "+91", - "phoneNumber" => $item->phone, - "callbackData" => $item->phone.'_'.$item->id, - "type" => "Template", - "template" => $template - ]; - DB::table('raphiwa_send')->insert(['to' => $item->phone, 'text' => json_encode($params) , 'contact_id' => $item->id, 'session_id' => $send->session_id]); + foreach ($response->data->items as $item) { + $this->comm->dispatchForContact($send, $item, $config); } } + DB::table('message_send')->where('id', $send->id)->update(['status' => 'send']); } } } + return 0; } } diff --git a/laravel/app/Console/Kernel.php b/laravel/app/Console/Kernel.php index 794cbcd..e5b66b4 100755 --- a/laravel/app/Console/Kernel.php +++ b/laravel/app/Console/Kernel.php @@ -27,6 +27,7 @@ class Kernel extends ConsoleKernel // $schedule->command('inspire')->hourly(); $schedule->command('whatsapp:send')->everyMinute(); + $schedule->command('email:send')->everyMinute(); $schedule->command('reminder:send')->everyMinute(); $schedule->command('token:refresh')->daily(); $schedule->command('scores:update')->everyTenMinutes(); diff --git a/laravel/app/Http/Controllers/BotController.php b/laravel/app/Http/Controllers/BotController.php index f5d70bd..28d8616 100644 --- a/laravel/app/Http/Controllers/BotController.php +++ b/laravel/app/Http/Controllers/BotController.php @@ -11,9 +11,22 @@ use App\Http\Requests\Creation; use Log; use DB; use App\Models\User; +use App\Services\SessionCommunicationService; class BotController extends LocalController { + protected $comm; + + public function __construct(SessionCommunicationService $comm) + { + $this->comm = $comm; + } + + protected function interaktAuthHeader(): string + { + return env('INTERAKT_API_AUTH', 'Basic S1BnVWoxRGZUcWpTYVl2OG9ZN3lEbkk2YjlXTzUtZXhvOWpMQ3RIN3ZJVTo='); + } + protected function sendMsg($number, $message) { Log::debug(print_r([$number, $message], true)); $params = ["countryCode" => "+91", @@ -22,11 +35,28 @@ class BotController extends LocalController "type" => "Text", "data" => ["message" => $message] ]; - $response = Http::withHeaders(['Authorization' => 'Basic S1BnVWoxRGZUcWpTYVl2OG9ZN3lEbkk2YjlXTzUtZXhvOWpMQ3RIN3ZJVTo=']) + $response = Http::withHeaders(['Authorization' => $this->interaktAuthHeader()]) ->post('https://api.interakt.ai/v1/public/message/', $params); Log::debug(print_r($response, true)); } + protected function deliverSessionText($number, $message, $session, $contact): void + { + if (!$session || !$contact) { + $this->sendMsg($number, $message); + return; + } + + $mode = $this->comm->resolveMode($session); + + if ($this->comm->shouldSendWhatsApp($mode)) { + $this->sendMsg($number, $message); + } + + if ($this->comm->shouldSendEmail($mode)) { + $this->comm->queueEmailFromText($session, $contact, $message); + } + } public function webhook(Request $request) { Log::debug('===== Webhook ======'); @@ -94,8 +124,9 @@ class BotController extends LocalController } else { $response = LambdaRequest::postrequest('contact', 'contact/update', ['id' => $uid, 'position'=>'india', 'is_verified' => true ], env('LAMBDA_ACCESS_TOKEN', 'default_value') ); - $session = (object)Session::where('code', $code)->first()->toArray(); - $reply = 'Greetings ' . $response->data->item->name ."\n"; + $session = Session::where('code', $code)->first(); + $contact = $response->data->item; + $reply = 'Greetings ' . $contact->name ."\n"; $reply .= 'You have Successfully registered for Crest session "'.$session->number.' ('.$session->type.') "'."\n"; $reply .= 'Please find the details below'."\n"; $reply .= 'Venue: ' . $session->venue . "\n"; @@ -107,8 +138,8 @@ class BotController extends LocalController } $reply .= "\n" . 'You can use this chat as your self assistance for getting details, download your certificate etc.'; Session::where('code', $code)->increment('reg_confirmed'); - Log::debug($reply); - Log::debug($code); + $this->deliverSessionText($number, $reply, $session, $contact); + return response()->json([]); } $this->sendMsg($number, $reply); @@ -235,21 +266,21 @@ class BotController extends LocalController if(isset($doctor->present) && $doctor->present == 'yes' && isset($doctor->feedback) && $doctor->feedback == 'yes') { $output = Creation::certificate($session_data['session_data'], $doctor); $filename = $output['filename']; + $certUrl = $domain . 'storage/certificate/'.$session_data['session_data']->type.'/'.$filename; + $sess = Session::find($session_data['session_data']->id); + + if ($sess) { + $this->comm->dispatchForContact($sess, $doctor, [ + 'id' => 0, + 'template_code' => 'crest_certificate_attached', + 'template_body' => "Dear {{RECIPIENT_NAME}},\n\nYour certificate for {{SESSION_NAME}} on {{SESSION_DATE}} is ready.\n\nView your certificate: " . $certUrl, + 'template_title' => 'Your CREST Certificate', + 'header_values' => [$certUrl], + 'body_params' => 'RECIPIENT_NAME||SESSION_NAME||SESSION_DATE', + 'button_params' => '', + ]); + } - $params = ["countryCode" => "+91", - "phoneNumber" => $number, - "callbackData" => "some_callbak", - "type" => "Template", - "template" => [ "name" => "crest_certificate_attached", - "languageCode" => "en", - "headerValues" => [$domain . 'storage/certificate/'.$session_data['session_data']->type.'/'.$filename], - "bodyValues" => [ $doctor->name, - $session_data['session_data']->number, - date("d M Y", strtotime($session_data['session_data']->start_date)) - ] - ] - ]; - DB::table('raphiwa_send')->insert(['to'=> $data['from'], 'text' => json_encode($params) ]); $reply = 'We will generate your certificate and send it to you in a while, please give us few minutes.'; } else if(isset($doctor->present) && $doctor->present == 'yes') { $reply = 'You have not filled up the feedback form sent to you. Please submit the feedback and request for certificate again.'; @@ -281,9 +312,38 @@ class BotController extends LocalController $data['msg_history'] = json_encode([$data['text']]); Rapiwha::create($data); } - $this->sendMsg($number, $reply); + $this->deliverBotReply($number, $reply, $session_data); return response()->json([]); } + + protected function deliverBotReply($number, $reply, array $session_data): void + { + if (!isset($session_data['session_data']) || !isset($session_data['user'])) { + $this->sendMsg($number, $reply); + return; + } + + $sess = Session::find($session_data['session_data']->id ?? 0); + if (!$sess) { + $this->sendMsg($number, $reply); + return; + } + + $response = LambdaRequest::getrequest( + 'contact', + 'contact/get_one', + ['id' => $session_data['user']], + env('LAMBDA_ACCESS_TOKEN', 'default_value') + ); + $contact = $response->data->item ?? null; + + if ($contact) { + $this->deliverSessionText($number, $reply, $sess, $contact); + } else { + $this->sendMsg($number, $reply); + } + } + public function receive(Request $request) { Log::debug('===== Receive ======'); $domain = 'https://road.xltr.in/'; diff --git a/laravel/app/Http/Controllers/MicrositeController.php b/laravel/app/Http/Controllers/MicrositeController.php index bcc2a69..8ede49d 100644 --- a/laravel/app/Http/Controllers/MicrositeController.php +++ b/laravel/app/Http/Controllers/MicrositeController.php @@ -9,6 +9,9 @@ use App\Models\Library; use App\Models\Session; use App\Models\Sessionagenda; use App\Models\Faculty; +use App\Mail\SessionMessage; +use App\Services\SessionCommunicationService; +use Illuminate\Support\Facades\Mail; use DB; use PDF; use Storage; @@ -170,7 +173,21 @@ class MicrositeController extends LocalController // $text .= "\n" . 'You can use this chat as your self assistance for getting details, download your certificate etc.'; // $res = $client->get('https://panel.rapiwha.com/send_message.php', ['query' => ['apikey' => 'Z4NXQUTBGV3P7D7W8OHX', 'number' => '91'.$post['phone'], 'text' => $text], 'timeout' => 10]); // $res = $res->getBody(); - $request->session()->put('session_user_id', $response->data->item->id); + $contactId = $response->data->item->id ?? null; + $request->session()->put('session_user_id', $contactId); + if (!empty($post['email'])) { + $request->session()->put('session_user_email', $post['email']); + } + if (!empty($post['name'])) { + $request->session()->put('session_user_name', $post['name']); + } + $request->session()->save(); + + $session = Session::where('code', $code)->first(); + $comm = app(SessionCommunicationService::class); + if ($session && !$linked_user && $comm->shouldSendEmail($comm->resolveMode($session))) { + $this->sendRegistrationVerificationEmail($session, $response->data->item->id, $post['email'], $post['name'] ?? ''); + } if($linked_user) { return redirect('/registersuccess')->withSuccess('Successfully registered for Session!!'); @@ -190,7 +207,131 @@ class MicrositeController extends LocalController $type = $request->session()->get('session_type'); $linked_user = $request->session()->get('linked_user'); $user_id = $request->session()->get('session_user_id'); - return view('road.success', ['code' => $code, 'type' => $type, 'user_id' => $user_id, 'linked_user' => $linked_user]); + $user_email = $request->session()->get('session_user_email'); + + $session = $code ? Session::where('code', $code)->first() : null; + $comm = app(SessionCommunicationService::class); + $communication_mode = $comm->resolveMode($session); + + return view('road.success', [ + 'code' => $code, + 'type' => $type, + 'user_id' => $user_id, + 'linked_user' => $linked_user, + 'communication_mode' => $communication_mode, + 'user_email' => $user_email, + 'email_verified' => $request->session()->get('email_verified', false), + 'microsite_url' => ($type && $code) ? url('/microsite/' . $type . '-' . $code) : null, + ]); + } + + public function verifyRegistration(Request $request, $code, $user_id) + { + $session = Session::where('code', $code)->first(); + if (!$session) { + return redirect('/')->withError('Invalid verification link.'); + } + + LambdaRequest::postrequest( + 'contact', + 'contact/update', + ['id' => $user_id, 'position' => 'india', 'is_verified' => true], + env('LAMBDA_ACCESS_TOKEN', 'default_value') + ); + + Session::where('code', $code)->increment('reg_confirmed'); + + $request->session()->put('session_code', $code); + $request->session()->put('session_type', $session->type); + $request->session()->put('email_verified', true); + $request->session()->put('is_linked_user', 'no'); + + return redirect('/registersuccess') + ->withSuccess('Your email has been confirmed successfully.'); + } + + public function resendVerificationEmail(Request $request) + { + $code = $request->input('code') ?: $request->session()->get('session_code'); + $user_id = $request->input('user_id') ?: $request->session()->get('session_user_id'); + $email = $request->input('email') ?: $request->session()->get('session_user_email'); + $name = $request->input('name') ?: $request->session()->get('session_user_name', ''); + + if ($code) { + $request->session()->put('session_code', $code); + } + if ($user_id) { + $request->session()->put('session_user_id', $user_id); + } + if ($email) { + $request->session()->put('session_user_email', $email); + } + + $session = $code ? Session::where('code', $code)->first() : null; + + if ($user_id && !$email) { + $contactResponse = LambdaRequest::getrequest( + 'contact', + 'contact/get_one', + ['id' => $user_id], + env('LAMBDA_ACCESS_TOKEN', 'default_value') + ); + if (!empty($contactResponse->data->item->email)) { + $email = $contactResponse->data->item->email; + $request->session()->put('session_user_email', $email); + } + if ($name === '' && !empty($contactResponse->data->item->name)) { + $name = $contactResponse->data->item->name; + } + } + + if (!$session || !$user_id || !$email) { + Log::warning('Resend verification missing data', [ + 'code' => $code, + 'user_id' => $user_id, + 'email' => $email, + ]); + return redirect('/registersuccess')->withError('Unable to resend verification email. Please register again.'); + } + + $sent = $this->sendRegistrationVerificationEmail($session, (string) $user_id, $email, $name); + + if (!$sent) { + return redirect('/registersuccess')->withError('Could not send verification email. Please check the email address and try again.'); + } + + return redirect('/registersuccess')->withSuccess('Verification email sent again. Please check your inbox.'); + } + + protected function sendRegistrationVerificationEmail(Session $session, string $userId, string $email, string $name): bool + { + $email = trim($email); + if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + return false; + } + + $verifyUrl = url('/verify-registration/' . $session->code . '/' . $userId); + $greeting = $name !== '' ? 'Hello ' . $name . ',' : 'Hello,'; + + $bodyHtml = '
' . e($greeting) . '
' + . 'Thank you for registering for ' . e($session->number) . '.
' + . 'Please confirm your email address by clicking the button below:
' + . '' + . 'Confirm your email' + . '
' + . 'Or copy this link into your browser:
' . e($verifyUrl) . '
If you did not register for this session, you can ignore this email.
' + . 'Regards,
CREST Team
'; print_r($data); exit;
+ $modeLabels = Session::communicationModes();
$dd = [];
foreach($data->toArray() as $d) {
+ $mode = $d['communication_mode'] ?? Session::MODE_WHATSAPP;
+ $d['communication_mode'] = $modeLabels[$mode] ?? 'WhatsApp';
$dd[] = (object)$d;
}
return $dd;
@@ -57,6 +61,7 @@ class SessionController extends LocalController
'start_date' => ['title' => 'Date', 'type' => 'date'],
'venue' => ['title' => 'Venue', 'type' => 'text'],
'status' => ['title' => 'Status', 'type' => 'select'],
+ 'communication_mode' => ['title' => 'Communication Mode', 'type' => 'text'],
];
}
@@ -81,7 +86,12 @@ class SessionController extends LocalController
'status' => ['title' => 'Status', 'type' => 'select', 'required' => true, 'values' => ['active' => 'Active', 'disabled' => 'Disabled'] ],
'join_link' => ['title' => 'Join Link', 'type' => 'text', 'fullwidth' => true],
'feedback_link' => ['title' => 'Feedback Link', 'type' => 'text', 'fullwidth' => true],
- 'survey_link' => ['title' => 'Survey Link', 'type' => 'text', 'fullwidth' => true],
+ 'survey_link' => ['title' => 'Survey Link', 'type' => 'text', 'fullwidth' => true],
+ 'communication_mode' => [
+ 'title' => 'Communication Mode',
+ 'type' => 'select',
+ 'values' => Session::communicationModes(),
+ ],
];
return $fields;
}
@@ -95,8 +105,8 @@ class SessionController extends LocalController
$data['start_date'] = $data['dates']['date'][0];
$data['dates'] = json_encode($data['dates']);
$data['faculty'] = '|'.implode('|',$data['faculty']).'|';
- //echo ''; print_r($data); exit;
- //$data['library_ids'] = implode(',', $data['library_ids']);
+ $comm = app(SessionCommunicationService::class);
+ $data['communication_mode'] = $comm->normalizeModeForSave($data['communication_mode'] ?? null);
return $data;
}
@@ -106,6 +116,9 @@ class SessionController extends LocalController
$values->dates = json_decode($values->dates);
$values->faculty = explode('|', $values->faculty);
$values->faculty = array_filter($values->faculty, function($value) { return !is_null($value) && $value !== ''; });
+ if (empty($values->communication_mode)) {
+ $values->communication_mode = Session::MODE_WHATSAPP;
+ }
}
//echo ''; print_r($values); exit;
return $values;
@@ -188,24 +201,105 @@ class SessionController extends LocalController
public function sendnotification(Request $request)
{
$session_id = $request->input('session_id');
- //echo $session_id; exit;
if (!$session_id) {
return response()->json(['status' => 'error', 'message' => 'Session ID is required.'], 400);
}
- // Send request to LambdaRequest with the session_id
- $response = LambdaRequest::signedrequest(
- 'get',
- 'mag',
- 'sessions/sendnotification',
- ['session_id' => $session_id]
- );
- //echo ''; print_r($response); exit;
- if (isset($response->status) && $response->status == 'success') {
- return response()->json(['status' => 'success', 'message' => 'Notification sent successfully.']);
- } else {
- $errorMsg = isset($response->message) ? $response->message : 'Failed to send notification.';
- return response()->json(['status' => 'error', 'message' => $errorMsg], 500);
+ $session = Session::find($session_id);
+ if (!$session) {
+ return response()->json(['status' => 'error', 'message' => 'Session not found.'], 404);
}
+
+ $comm = app(SessionCommunicationService::class);
+ $mode = $comm->resolveMode($session);
+
+ $response = null;
+ if ($comm->shouldSendWhatsApp($mode)) {
+ $response = LambdaRequest::signedrequest(
+ 'get',
+ 'mag',
+ 'sessions/sendnotification',
+ [
+ 'session_id' => $session_id,
+ 'communication_mode' => $mode,
+ ]
+ );
+ }
+
+ $emailQueued = 0;
+ if ($comm->shouldSendEmail($mode)) {
+ $emailQueued = $this->sendnotificationEmail($session);
+ }
+
+ $waOk = !$comm->shouldSendWhatsApp($mode)
+ || (isset($response->status) && $response->status == 'success');
+
+ if ($waOk || $emailQueued > 0) {
+ return response()->json(['status' => 'success', 'message' => 'Notification sent successfully.']);
+ }
+
+ $errorMsg = isset($response->message) ? $response->message : 'Failed to send notification.';
+ return response()->json(['status' => 'error', 'message' => $errorMsg], 500);
+ }
+
+ protected function sendnotificationEmail(Session $session): int
+ {
+ $comm = app(SessionCommunicationService::class);
+ $response = LambdaRequest::getrequest(
+ 'contact',
+ 'contact/get_all',
+ [
+ 'source' => 'dynamodb',
+ 'search' => json_encode(['session_code' => $session->code]),
+ 'fields' => json_encode(['session_code', 'email', 'name', 'phone', 'id']),
+ ],
+ env('LAMBDA_ACCESS_TOKEN', 'default_value')
+ );
+
+ if (empty($response->data->items)) {
+ return 0;
+ }
+
+ $message = $this->buildSessionNotificationText($session);
+ $queued = 0;
+
+ foreach ($response->data->items as $contact) {
+ if ($comm->queueEmailFromText($session, $contact, $message, 'session_notification')) {
+ $queued++;
+ }
+ }
+
+ return $queued;
+ }
+
+ protected function buildSessionNotificationText(Session $session): string
+ {
+ $lines = [
+ 'Greetings,',
+ '',
+ 'This is a notification regarding your Crest session "' . $session->number . ' (' . $session->type . ')".',
+ '',
+ 'Venue: ' . $session->venue,
+ 'Address: ' . ($session->address ?? ''),
+ '',
+ 'Timings:',
+ ];
+
+ $dates = json_decode($session->dates);
+ if ($dates && isset($dates->date)) {
+ foreach ($dates->date as $i => $date) {
+ $lines[] = date('d M, Y', strtotime($date))
+ . ' at ' . date('h:iA', strtotime($dates->start[$i]))
+ . ' - ' . date('h:iA', strtotime($dates->end[$i]));
+ }
+ }
+
+ $lines[] = '';
+ $lines[] = 'Session website: https://road.xltr.in/microsite/' . $session->type . '-' . $session->code;
+ $lines[] = '';
+ $lines[] = 'Regards,';
+ $lines[] = 'CREST Team';
+
+ return implode("\n", $lines);
}
}
diff --git a/laravel/app/Mail/SessionMessage.php b/laravel/app/Mail/SessionMessage.php
new file mode 100644
index 0000000..1a9dc07
--- /dev/null
+++ b/laravel/app/Mail/SessionMessage.php
@@ -0,0 +1,55 @@
+subjectLine = $subject;
+ $this->bodyHtml = $bodyHtml;
+ }
+
+ public function build()
+ {
+ $fromAddress = env('CREST_MAIL_FROM', config('mail.from.address'));
+ $fromName = env('CREST_MAIL_FROM_NAME', config('mail.from.name', 'CREST'));
+
+ $logoUrl = $this->resolveLogoUrl();
+
+ return $this->view('email.session-message')
+ ->from($fromAddress, $fromName)
+ ->replyTo($fromAddress, $fromName)
+ ->subject($this->subjectLine)
+ ->with([
+ 'bodyHtml' => $this->bodyHtml,
+ 'logoUrl' => $logoUrl,
+ ]);
+ }
+
+ protected function resolveLogoUrl(): string
+ {
+ $candidates = [
+ public_path('images/crest-logo.jpeg'),
+ public_path('assets/images/crest-logo.jpeg'),
+ public_path('road/brochure/crest-logo.png'),
+ ];
+
+ foreach ($candidates as $path) {
+ if (file_exists($path)) {
+ return asset(str_replace(public_path(), '', $path));
+ }
+ }
+
+ return 'https://road.xltr.in/images/crest-logo.jpeg';
+ }
+}
diff --git a/laravel/app/Models/EmailSend.php b/laravel/app/Models/EmailSend.php
new file mode 100644
index 0000000..fe2d698
--- /dev/null
+++ b/laravel/app/Models/EmailSend.php
@@ -0,0 +1,22 @@
+ 'WhatsApp',
+ self::MODE_EMAIL => 'Email',
+ self::MODE_BOTH => 'Both',
+ ];
+ }
+
public $table = 'sessions';
const CREATED_AT = 'created_at';
@@ -35,5 +48,6 @@ class Session extends Model
'join_link',
'feedback_link',
'survey_link',
+ 'communication_mode',
];
}
diff --git a/laravel/app/Services/SessionCommunicationService.php b/laravel/app/Services/SessionCommunicationService.php
new file mode 100644
index 0000000..e76e659
--- /dev/null
+++ b/laravel/app/Services/SessionCommunicationService.php
@@ -0,0 +1,346 @@
+communication_mode ?? null)
+ : ($session['communication_mode'] ?? null);
+
+ $mode = strtolower(trim((string) $mode));
+
+ if (!in_array($mode, [Session::MODE_WHATSAPP, Session::MODE_EMAIL, Session::MODE_BOTH], true)) {
+ return Session::MODE_WHATSAPP;
+ }
+
+ return $mode;
+ }
+
+ public function shouldSendWhatsApp(string $mode): bool
+ {
+ return in_array($mode, [Session::MODE_WHATSAPP, Session::MODE_BOTH], true);
+ }
+
+ public function shouldSendEmail(string $mode): bool
+ {
+ return in_array($mode, [Session::MODE_EMAIL, Session::MODE_BOTH], true);
+ }
+
+ public function normalizeModeForSave(?string $mode): string
+ {
+ $mode = strtolower(trim((string) $mode));
+
+ if (!in_array($mode, [Session::MODE_WHATSAPP, Session::MODE_EMAIL, Session::MODE_BOTH], true)) {
+ return Session::MODE_WHATSAPP;
+ }
+
+ return $mode;
+ }
+
+ public function buildSessionVariables($session, $sendRow = null): array
+ {
+ $session = is_object($session) ? $session : (object) $session;
+
+ return [
+ 'SESSION_NAME' => $session->number ?? '',
+ 'SESSION_DATE' => isset($session->start_date)
+ ? date('d M, Y', strtotime($session->start_date))
+ : '',
+ 'SESSION_TYPE' => strtoupper($session->type ?? ''),
+ 'SESSION_BROCHURE' => 'https://road.xltr.in/brochure/' . ($session->code ?? ''),
+ 'SESSION_CODE' => $session->code ?? '',
+ ];
+ }
+
+ public function applyFeedbackSessionCode(string $sessionCode, $contact, string $templateCode): string
+ {
+ if (!in_array($templateCode, self::$feedbackTemplateCodes, true)) {
+ return $sessionCode;
+ }
+
+ $contactId = is_object($contact) ? ($contact->id ?? '') : ($contact['id'] ?? '');
+
+ return $sessionCode . '_' . $contactId;
+ }
+
+ public function buildContactVariables($contact, $session, $sendRow): array
+ {
+ $sessionVars = $this->buildSessionVariables($session, $sendRow);
+ $templateCode = is_object($sendRow)
+ ? ($sendRow->template_code ?? '')
+ : ($sendRow['template_code'] ?? '');
+
+ $sessionCode = $this->applyFeedbackSessionCode(
+ $sessionVars['SESSION_CODE'],
+ $contact,
+ $templateCode
+ );
+
+ $sendId = is_object($sendRow) ? ($sendRow->id ?? 0) : ($sendRow['id'] ?? 0);
+
+ return array_merge($sessionVars, [
+ 'SESSION_CODE' => $sessionCode,
+ 'RECIPIENT_NAME' => is_object($contact) ? ($contact->name ?? '') : ($contact['name'] ?? ''),
+ 'CERT_PARAM' => (is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '')) . '_' . $sendId,
+ ]);
+ }
+
+ public function substituteParams(?string $pipeList, array $vars): array
+ {
+ if ($pipeList === null || $pipeList === '') {
+ return [];
+ }
+
+ $params = explode('||', $pipeList);
+ foreach ($params as &$var) {
+ if (isset($vars[$var]) && $vars[$var] !== '') {
+ $var = $vars[$var];
+ }
+ }
+
+ return $params;
+ }
+
+ public function renderTemplateBody(string $body, array $vars): string
+ {
+ $rendered = $body;
+
+ foreach ($vars as $key => $value) {
+ $rendered = str_replace('{{' . $key . '}}', $value, $rendered);
+ $rendered = str_replace('{' . $key . '}', $value, $rendered);
+ }
+
+ return $rendered;
+ }
+
+ public function buildButtonSection(array $buttonValues): string
+ {
+ if (empty($buttonValues)) {
+ return '';
+ }
+
+ $lines = ["\n"];
+ foreach ($buttonValues as $i => $value) {
+ $value = trim((string) $value);
+ if ($value === '') {
+ continue;
+ }
+ if (filter_var($value, FILTER_VALIDATE_URL)) {
+ $lines[] = 'Link: ' . $value;
+ } else {
+ $lines[] = $value;
+ }
+ }
+
+ return implode("\n", $lines);
+ }
+
+ public function buildInteraktTemplate(array $config, array $vars): array
+ {
+ if (!empty($config['header_values']) && is_array($config['header_values'])) {
+ $headerParams = $config['header_values'];
+ } else {
+ $headerParams = $this->substituteParams($config['header_params'] ?? '', $vars);
+ }
+
+ $bodyParams = $this->substituteParams($config['body_params'] ?? '', $vars);
+ $buttonParams = $this->substituteParams($config['button_params'] ?? '', $vars);
+
+ $template = [
+ 'name' => $config['template_code'],
+ 'languageCode' => 'en',
+ ];
+
+ if (count($headerParams) > 0) {
+ $template['headerValues'] = $headerParams;
+ }
+ if (count($bodyParams) > 0) {
+ $template['bodyValues'] = $bodyParams;
+ }
+ if (count($buttonParams) > 0) {
+ $template['buttonValues'] = (object) ['0' => $buttonParams];
+ }
+
+ return $template;
+ }
+
+ public function buildEmailBody(string $templateBody, array $vars, array $buttonValues = [], array $headerValues = []): string
+ {
+ $body = $this->renderTemplateBody($templateBody, $vars);
+ $body .= $this->buildButtonSection($buttonValues);
+
+ $imageHtml = '';
+ foreach ($headerValues as $headerValue) {
+ if (filter_var($headerValue, FILTER_VALIDATE_URL)) {
+ $imageHtml .= ' . ')
';
+ }
+ }
+
+ return $imageHtml . nl2br(e($body));
+ }
+
+ public function buildEmailSubject($session, $sendRow): string
+ {
+ $sessionName = is_object($session) ? ($session->number ?? 'Crest Session') : ($session['number'] ?? 'Crest Session');
+ $title = is_object($sendRow) ? ($sendRow->template_title ?? null) : ($sendRow['template_title'] ?? null);
+
+ if (!empty($title)) {
+ return 'Crest – ' . $title;
+ }
+
+ return 'Crest – ' . $sessionName;
+ }
+
+ public function contactEmail($contact): ?string
+ {
+ $email = is_object($contact) ? ($contact->email ?? '') : ($contact['email'] ?? '');
+ $email = trim((string) $email);
+
+ if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ return null;
+ }
+
+ return $email;
+ }
+
+ public function queueWhatsApp($contact, int $sessionId, array $interaktPayload): void
+ {
+ $phone = is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '');
+ $contactId = is_object($contact) ? ($contact->id ?? null) : ($contact['id'] ?? null);
+
+ DB::table('raphiwa_send')->insert([
+ 'to' => $phone,
+ 'text' => json_encode($interaktPayload),
+ 'contact_id' => $contactId,
+ 'session_id' => $sessionId,
+ 'is_sent' => 0,
+ 'status' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+ }
+
+ public function queueEmail($contact, int $sessionId, string $subject, string $bodyHtml, ?string $templateCode = null): bool
+ {
+ $email = $this->contactEmail($contact);
+ if (!$email) {
+ return false;
+ }
+
+ $contactId = is_object($contact) ? ($contact->id ?? null) : ($contact['id'] ?? null);
+
+ DB::table('email_send')->insert([
+ 'to' => $email,
+ 'subject' => $subject,
+ 'body_html' => $bodyHtml,
+ 'contact_id' => $contactId,
+ 'session_id' => $sessionId,
+ 'template_code' => $templateCode,
+ 'is_sent' => 0,
+ 'status' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+
+ return true;
+ }
+
+ public function queueEmailFromText($session, $contact, string $plainText, ?string $templateCode = null): bool
+ {
+ if (!$this->shouldSendEmail($this->resolveMode($session))) {
+ return false;
+ }
+
+ $sessionId = is_object($session) ? (int) ($session->id ?? 0) : (int) ($session['id'] ?? 0);
+ $subject = 'Crest – ' . (is_object($session) ? ($session->number ?? 'Message') : ($session['number'] ?? 'Message'));
+ $bodyHtml = nl2br(e($plainText));
+
+ return $this->queueEmail($contact, $sessionId, $subject, $bodyHtml, $templateCode);
+ }
+
+ public function dispatchForContact($session, $contact, array $config): void
+ {
+ $mode = $this->resolveMode($session);
+ $sessionId = is_object($session) ? (int) ($session->id ?? 0) : (int) ($session['id'] ?? 0);
+ $vars = $this->buildContactVariables($contact, $session, $config);
+
+ if (!empty($config['header_values']) && is_array($config['header_values'])) {
+ $headerParams = $config['header_values'];
+ } else {
+ $headerParams = $this->substituteParams($config['header_params'] ?? '', $vars);
+ }
+
+ $bodyParams = $this->substituteParams($config['body_params'] ?? '', $vars);
+ $buttonParams = $this->substituteParams($config['button_params'] ?? '', $vars);
+
+ if ($this->shouldSendWhatsApp($mode)) {
+ $phone = is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '');
+ $contactId = is_object($contact) ? ($contact->id ?? '') : ($contact['id'] ?? '');
+
+ $template = $this->buildInteraktTemplate($config, $vars);
+ $params = [
+ 'countryCode' => '+91',
+ 'phoneNumber' => $phone,
+ 'callbackData' => $phone . '_' . $contactId,
+ 'type' => 'Template',
+ 'template' => $template,
+ ];
+
+ $this->queueWhatsApp($contact, $sessionId, $params);
+ }
+
+ if ($this->shouldSendEmail($mode)) {
+ $templateBody = $config['template_body'] ?? '';
+ if ($templateBody !== '') {
+ $bodyHtml = $this->buildEmailBody($templateBody, $vars, $buttonParams, $headerParams);
+ $subject = $this->buildEmailSubject($session, $config);
+ $this->queueEmail(
+ $contact,
+ $sessionId,
+ $subject,
+ $bodyHtml,
+ $config['template_code'] ?? null
+ );
+ }
+ }
+ }
+
+ public function dispatchPlainText($session, $contact, string $message, bool $sendWhatsApp = true): void
+ {
+ $mode = $this->resolveMode($session);
+ $sessionId = is_object($session) ? (int) ($session->id ?? 0) : (int) ($session['id'] ?? 0);
+
+ if ($sendWhatsApp && $this->shouldSendWhatsApp($mode)) {
+ $phone = is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '');
+ $params = [
+ 'countryCode' => '+91',
+ 'phoneNumber' => $phone,
+ 'callbackData' => 'email_mirror',
+ 'type' => 'Text',
+ 'data' => ['message' => $message],
+ ];
+ $this->queueWhatsApp($contact, $sessionId, $params);
+ }
+
+ if ($this->shouldSendEmail($mode)) {
+ $this->queueEmailFromText($session, $contact, $message);
+ }
+ }
+}
diff --git a/laravel/database/migrations/2026_05_18_000001_add_session_communication_mode.php b/laravel/database/migrations/2026_05_18_000001_add_session_communication_mode.php
new file mode 100644
index 0000000..af6bced
--- /dev/null
+++ b/laravel/database/migrations/2026_05_18_000001_add_session_communication_mode.php
@@ -0,0 +1,44 @@
+string('communication_mode', 20)->default('whatsapp')->nullable()->after('survey_link');
+ });
+ }
+
+ if (!Schema::hasTable('email_send')) {
+ Schema::create('email_send', function (Blueprint $table) {
+ $table->id();
+ $table->string('to');
+ $table->string('subject');
+ $table->longText('body_html');
+ $table->string('contact_id')->nullable();
+ $table->unsignedBigInteger('session_id')->default(0);
+ $table->string('template_code')->nullable();
+ $table->tinyInteger('is_sent')->default(0);
+ $table->text('response')->nullable();
+ $table->tinyInteger('status')->default(1);
+ $table->timestamps();
+ });
+ }
+ }
+
+ public function down()
+ {
+ if (Schema::hasTable('sessions') && Schema::hasColumn('sessions', 'communication_mode')) {
+ Schema::table('sessions', function (Blueprint $table) {
+ $table->dropColumn('communication_mode');
+ });
+ }
+
+ Schema::dropIfExists('email_send');
+ }
+}
diff --git a/laravel/docs/LAMBDA_SENDNOTIFICATION.md b/laravel/docs/LAMBDA_SENDNOTIFICATION.md
new file mode 100644
index 0000000..0ee0169
--- /dev/null
+++ b/laravel/docs/LAMBDA_SENDNOTIFICATION.md
@@ -0,0 +1,20 @@
+# Lambda: sessions/sendnotification
+
+The Laravel admin **Send notification** action calls `mag/sessions/sendnotification` with:
+
+- `session_id` — session primary key
+- `communication_mode` — `whatsapp`, `email`, or `both`
+
+## Expected Lambda behaviour
+
+| Mode | WhatsApp (existing) | Email |
+|------|---------------------|-------|
+| `whatsapp` | Yes | No |
+| `email` | No | No (handled in Laravel) |
+| `both` | Yes | No (handled in Laravel) |
+
+When `communication_mode` is `email` or `both`, Laravel queues participant emails via `email_send` and the `email:send` cron command. Lambda should skip WhatsApp when mode is `email` only.
+
+## Laravel fallback
+
+If Lambda cannot send email, no change is required: Laravel builds the notification body from session details and queues mail for all contacts with a valid email address.
diff --git a/laravel/resources/views/email/session-message.blade.php b/laravel/resources/views/email/session-message.blade.php
new file mode 100644
index 0000000..7586d18
--- /dev/null
+++ b/laravel/resources/views/email/session-message.blade.php
@@ -0,0 +1,36 @@
+
+
+
+
+
+ CREST
+
+
+| + + | +
You have successfully registered for the Session.
- @if(Session::get('is_linked_user') == 'no') -Please click on the link below to verify your Whatsapp number. This will open whatsapp and create a message, you just have to send the message without changing it.
- Verify Whatsapp Number +{{ session('success') }}
+ @endif + @if(session('error')) +{{ session('error') }}
+ @endif + + @if(!empty($email_verified)) ++ Your email address has been verified. You are now registered for this CREST session. +
+ @if(!empty($microsite_url)) + + + Visit session website + + @endif + @else -You can now go back to the app and refresh the Sessions page to view pre-course and other related content.
+You have successfully registered for the Session.
+ + @if(Session::get('is_linked_user') == 'no') + @php + $mode = $communication_mode ?? 'whatsapp'; + @endphp + + @if($mode === 'email') ++ Please confirm your email address. We have sent a verification link to + @if(!empty($user_email)) + {{ $user_email }}. + @else + the email address you provided. + @endif + Click the link in that email to complete your registration. +
+ @if(!empty($code) && !empty($user_id)) + + @else +Please complete registration again to resend the verification email.
+ @endif + + @elseif($mode === 'both') ++ Please confirm your email address. We have sent a verification link to + @if(!empty($user_email)) + {{ $user_email }}. + @else + the email address you provided. + @endif +
+ @if(!empty($code) && !empty($user_id)) + + @endif ++ You can also verify your WhatsApp number by clicking below. This will open WhatsApp with a pre-filled message — send it without changing the text. +
+ + + Verify WhatsApp number + + + @else ++ Please click the link below to verify your WhatsApp number. This will open WhatsApp and create a message — send it without changing the text. +
+ + + Verify WhatsApp number + + @endif + @else +You can now go back to the app and refresh the Sessions page to view pre-course and other related content.
+ @endif @endif