getBody(), true); if (!is_array($body)) { return ResponseLib::sendFail('Invalid JSON body', [], 'E_VALIDATE')->withStatus(400); } $templateName = trim((string)($body['mail_template'] ?? '')); $config = $body['mail_config'] ?? null; if ($templateName === '' || !is_array($config)) { return ResponseLib::sendFail('Missing mail_template or mail_config', [], 'E_VALIDATE')->withStatus(400); } // Required fields for sending $toEmail = trim((string)($config['to_email'] ?? '')); $toName = trim((string)($config['to_name'] ?? '')); $subject = trim((string)($config['subject'] ?? '')); if ($toEmail === '' || $subject === '') { return ResponseLib::sendFail('Missing to_email or subject', [], 'E_VALIDATE')->withStatus(400); } $fromEmail = trim((string)($_ENV['MAIL_FROM'] ?? 'no-reply@example.com')); $fromName = trim((string)($_ENV['MAIL_FROM_NAME'] ?? 'No Reply')); // Load template file from templates/{name}.tpl $tplPath = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . $templateName . '.tpl'; if (!is_file($tplPath)) { return ResponseLib::sendFail('Template not found', ['template' => $templateName], 'E_NOT_FOUND')->withStatus(404); } $tpl = file_get_contents($tplPath); if ($tpl === false) { return ResponseLib::sendFail('Failed to load template', ['template' => $templateName], 'E_IO')->withStatus(500); } // Simple placeholder substitution: {{key}} $replacements = []; foreach ($config as $k => $v) { if (is_scalar($v)) { $replacements['{{' . $k . '}}'] = (string)$v; } } $bodyText = strtr($tpl, $replacements); // Build RFC 5322 message $headers = []; $headers[] = sprintf('From: %s <%s>', $fromName !== '' ? $fromName : $fromEmail, $fromEmail); $headers[] = sprintf('To: %s <%s>', $toName !== '' ? $toName : $toEmail, $toEmail); $headers[] = sprintf('Subject: %s', $subject); $headers[] = 'MIME-Version: 1.0'; $headers[] = 'Content-Type: text/plain; charset=UTF-8'; $headers[] = 'Content-Transfer-Encoding: 8bit'; $message = implode("\r\n", $headers) . "\r\n\r\n" . $bodyText . "\r\n"; // Write to temp file $tmpFile = tempnam(sys_get_temp_dir(), 'mail_'); if ($tmpFile === false) { return ResponseLib::sendFail('Failed to create temp file', [], 'E_IO')->withStatus(500); } file_put_contents($tmpFile, $message); // Execute sendmail (postfix) $sendmail = $_ENV['SENDMAIL_PATH'] ?? '/usr/sbin/sendmail'; $cmd = sprintf("%s -t -i < %s", escapeshellcmd($sendmail), escapeshellarg($tmpFile)); try { [$code, $output] = await(ExecLib::exec($cmd, (float)($_ENV['SENDMAIL_TIMEOUT'] ?? 10))); } catch (\Throwable $e) { @unlink($tmpFile); return ResponseLib::sendFail('Sendmail failed', ['error' => $e->getMessage()], 'E_SENDMAIL')->withStatus(500); } finally { @unlink($tmpFile); } if ($code !== 0) { return ResponseLib::sendFail('Sendmail exited with non-zero', ['code' => $code, 'output' => $output], 'E_SENDMAIL')->withStatus(500); } return ResponseLib::sendOk([ 'status' => 'sent', 'to' => $toEmail, 'template' => $templateName, 'code' => $code, ]); } }