From 006f86387b218cd946f076041c51c69cd23314e1 Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Sun, 31 May 2026 18:35:21 +0200 Subject: [PATCH] [FIX] EmailService: aggiungi sendViaTemplate() (era mancante - Edit fallito in de09af6) Il metodo sendViaTemplate() non era stato salvato (Edit silenziosamente fallito): requestOtp() del portale chiamava un metodo inesistente -> errore inghiottito dal try/catch -> OTP MAI inviato. Ora presente: POST /api/emails/send con template + data (campo canonico relay AgileHub) + alias vars, senza logEmail (OTP fuori da email_log). Co-Authored-By: Claude Opus 4.8 --- application/services/EmailService.php | 66 +++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/application/services/EmailService.php b/application/services/EmailService.php index 4afd7f0..3859666 100644 --- a/application/services/EmailService.php +++ b/application/services/EmailService.php @@ -118,6 +118,72 @@ class EmailService return true; } + /** + * Invia via TEMPLATE del relay AgileHub (POST /api/emails/send), NON send-raw. + * A differenza di send()/sendViaRelay() NON chiama logEmail(): pensato per + * messaggi che non devono lasciare traccia del contenuto a DB (es. OTP del + * portale fornitore). Il corpo e' renderizzato lato relay da template+data. + * + * @param string $to destinatario + * @param string $template slug template registrato sul relay (es. "supplier_otp") + * @param array $vars variabili di sostituzione del template + * @param string|null $brandName nome committente per branding (opzionale) + * @return bool true se il relay ha accettato (HTTP 2xx + success) + */ + public function sendViaTemplate(string $to, string $template, array $vars, ?string $brandName = null): bool + { + $base = rtrim(self::env('EMAIL_MS_URL', 'https://agilehub.agile.software/api/emails'), '/'); + $key = self::env('INTERNAL_EMAIL_KEY', ''); + + if ($key === '') { + error_log('[EmailService] INTERNAL_EMAIL_KEY non configurata: template "' . $template . '" saltato per ' . self::maskEmail($to)); + return false; + } + + $payload = json_encode([ + 'to' => $to, + 'template' => $template, + 'data' => $vars, // campo canonico standard email-relay AgileHub (Handlebars) + 'vars' => $vars, // alias difensivo (alcune versioni del relay leggono "vars") + 'product' => 'nis2', + 'brand_name' => $brandName, + 'priority' => 'transactional', + ], JSON_UNESCAPED_UNICODE); + + $ch = curl_init($base . '/send'); // endpoint TEMPLATE (NON /send-raw) + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'X-Internal-Key: ' . $key, + ], + ]); + $body = curl_exec($ch); + $errno = curl_errno($ch); + $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($errno !== 0) { + error_log('[EmailService] relay template unreachable (' . curl_strerror($errno) . ') per ' . self::maskEmail($to)); + return false; + } + if ($httpCode < 200 || $httpCode >= 300) { + error_log('[EmailService] relay template HTTP ' . $httpCode . ' per ' . self::maskEmail($to) . ': ' . substr((string) $body, 0, 200)); + return false; + } + $decoded = json_decode((string) $body, true); + if (is_array($decoded) && array_key_exists('success', $decoded) && !$decoded['success']) { + error_log('[EmailService] relay template success=false per ' . self::maskEmail($to) . ': ' . substr((string) $body, 0, 200)); + return false; + } + // NESSUN logEmail(): il contenuto (OTP) non deve finire in email_log. + return true; + } + /** Maschera l'email per i log (GDPR): m***@dominio.it. */ private static function maskEmail(string $email): string {