resolveTemplate($type, $organisation); $template = $this->substituteVariables($template, $variables); $branding = $this->resolveBranding($organisation); $emailLog = EmailLog::create([ 'organisation_id' => $organisation?->id, 'event_id' => $eventId, 'person_id' => $personId, 'user_id' => $userId, 'recipient_email' => $recipientEmail, 'recipient_name' => $recipientName, 'mailable_class' => TransactionalMail::class, 'template_type' => $type->value, 'subject' => $template['subject'], 'status' => EmailLogStatus::QUEUED->value, 'queued_at' => now(), 'triggered_by_user_id' => $triggeredByUserId, ]); SendTransactionalEmail::dispatch( emailLogId: $emailLog->id, type: $type, recipientEmail: $recipientEmail, recipientName: $recipientName, template: $template, branding: $branding, actionUrl: $actionUrl, ); return $emailLog; } /** * Resolve template: org override or system defaults. * * @return array{subject: string, heading: string|null, body_text: string, button_text: string|null, is_custom: bool} */ public function resolveTemplate(EmailTemplateType $type, ?Organisation $organisation = null): array { if ($organisation) { $override = OrganisationEmailTemplate::where('organisation_id', $organisation->id) ->where('type', $type->value) ->first(); if ($override) { return [ 'subject' => $override->subject, 'heading' => $override->heading, 'body_text' => $override->body_text, 'button_text' => $override->button_text, 'is_custom' => true, ]; } } $defaults = $type->defaults(); $defaults['is_custom'] = false; return $defaults; } /** * Resolve organisation branding or system defaults. * * @return array{logo_url: string|null, primary_color: string, secondary_color: string, footer_text: string, reply_to_email: string|null, reply_to_name: string|null} */ public function resolveBranding(?Organisation $organisation = null): array { $defaults = [ 'logo_url' => null, 'primary_color' => '#6366F1', 'secondary_color' => '#4F46E5', 'footer_text' => '© ' . date('Y') . ' Crewli', 'reply_to_email' => null, 'reply_to_name' => null, ]; if (! $organisation) { return $defaults; } $settings = OrganisationEmailSettings::where('organisation_id', $organisation->id)->first(); if (! $settings) { $defaults['footer_text'] = '© ' . date('Y') . ' ' . $organisation->name; return $defaults; } return [ 'logo_url' => $settings->logo_url ?? $defaults['logo_url'], 'primary_color' => $settings->primary_color ?? $defaults['primary_color'], 'secondary_color' => $settings->secondary_color ?? $defaults['secondary_color'], 'footer_text' => $settings->footer_text ?? ('© ' . date('Y') . ' ' . $organisation->name), 'reply_to_email' => $settings->reply_to_email, 'reply_to_name' => $settings->reply_to_name, ]; } /** * Get all template types with their current content for an organisation. * * @return list */ public function getAllTemplates(?Organisation $organisation = null): array { $result = []; foreach (EmailTemplateType::cases() as $type) { $template = $this->resolveTemplate($type, $organisation); $template['type'] = $type->value; $template['label'] = $type->label(); $template['defaults'] = $type->defaults(); $result[] = $template; } return $result; } /** * Substitute {variable} placeholders in template text. */ private function substituteVariables(array $template, array $variables): array { foreach ($template as $key => $value) { if (is_string($value)) { foreach ($variables as $var => $replacement) { $value = str_replace('{' . $var . '}', (string) $replacement, $value); } $template[$key] = $value; } } return $template; } }