getenv('EMAILTEMPLATE_SENDER_TOKEN') ?: 'DWFE3f5NQTZH5phtVrOWRf4BL3bmrrPRk1LUkorAwPwjttfcFgzkhe4LtL1n3jYA', 'template_api' => [ 'base_url' => getenv('EMAILTEMPLATE_API_BASE') ?: 'https://staging.emailtemplate.it/external/render', 'token' => getenv('EMAILTEMPLATE_API_TOKEN') ?: 'qYLV9OLOJ48WPs0CLrnOWpkok1o1wRN_kGsdMVzumzPEYQP-rHxhnn0YlSWGW5hH', 'timeout' => 15, ], 'db' => [ 'dsn' => getenv('EMAILTEMPLATE_LOCAL_DSN') ?: 'mysql:host=127.0.0.1;dbname=d0444c25;charset=utf8mb4', 'user' => getenv('EMAILTEMPLATE_LOCAL_DB_USER') ?: 'root', 'pass' => getenv('EMAILTEMPLATE_LOCAL_DB_PASS') ?: '/7ü9+§ÄfkiQvGPr§2Op7', 'options' => [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ], 'enabled' => true, ], 'mail' => [ 'from_email' => 'no-reply@example.com', 'from_name' => 'EmailTemplate Sender', 'transport' => 'mail', // aktuell nur mail() ], ]; $localSenderOverride = __DIR__ . '/emailtemplate.sender.conf.php'; if (is_file($localSenderOverride)) { $override = include $localSenderOverride; if (is_array($override)) { $senderConfig = array_replace_recursive($senderConfig, $override); } } function senderRespond($payload, int $status = 200): void { http_response_code($status); header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store, max-age=0'); echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function senderRequireToken(array $config): void { $expected = (string)($config['token'] ?? ''); if ($expected === '') { senderRespond(['ok' => false, 'error' => 'Sender token not configured'], 500); } $provided = null; if (!empty($_SERVER['HTTP_AUTHORIZATION']) && stripos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer ') === 0) { $provided = trim(substr($_SERVER['HTTP_AUTHORIZATION'], 7)); } elseif (!empty($_SERVER['HTTP_X_EMAILTEMPLATE_TOKEN'])) { $provided = trim($_SERVER['HTTP_X_EMAILTEMPLATE_TOKEN']); } elseif (isset($_POST['token'])) { $provided = (string)$_POST['token']; } if (!$provided || !hash_equals($expected, $provided)) { senderRespond(['ok' => false, 'error' => 'Unauthorized'], 403); } } function senderInput(): array { $raw = file_get_contents('php://input'); if ($raw !== false && trim($raw) !== '') { $decoded = json_decode($raw, true); if (is_array($decoded)) { return $decoded; } } if (!empty($_POST)) { return $_POST; } return []; } function senderDb(array $config): ?PDO { if (empty($config['db']['enabled'])) { return null; } static $pdo = null; if ($pdo instanceof PDO) { return $pdo; } try { $pdo = new PDO( $config['db']['dsn'], $config['db']['user'], $config['db']['pass'], $config['db']['options'] ); } catch (Throwable $e) { senderRespond(['ok' => false, 'error' => 'DB connection failed', 'detail' => $e->getMessage()], 500); } return $pdo; } function resolvePlaceholderValue($definition, ?PDO $pdo) { if (is_scalar($definition)) { return $definition; } if (!is_array($definition)) { return null; } if (array_key_exists('value', $definition)) { return $definition['value']; } if (($definition['source'] ?? '') === 'db') { if (!$pdo) { throw new RuntimeException('Database access disabled'); } $table = $definition['table'] ?? null; $column = $definition['column'] ?? null; $where = $definition['where'] ?? []; if (!$table || !$column || !is_array($where) || !$where) { throw new InvalidArgumentException('Invalid DB placeholder definition'); } $conditions = []; $params = []; foreach ($where as $key => $value) { $param = ':w_' . preg_replace('/[^a-z0-9_]/i', '_', $key); $conditions[] = sprintf('`%s` = %s', str_replace('`', '', $key), $param); $params[$param] = $value; } $sql = sprintf( 'SELECT `%s` FROM `%s` WHERE %s LIMIT 1', str_replace('`', '', $column), str_replace('`', '', $table), implode(' AND ', $conditions) ); $stmt = $pdo->prepare($sql); foreach ($params as $param => $value) { $stmt->bindValue($param, $value); } $stmt->execute(); return $stmt->fetchColumn(); } return null; } function fetchTemplateHtml(array $config, array $payload): array { $apiUrl = $config['template_api']['base_url']; $apiToken = $config['template_api']['token']; if (!$apiUrl || !$apiToken) { senderRespond(['ok' => false, 'error' => 'Template API not configured'], 500); } $body = $payload; $body['token'] = $apiToken; $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json\r\n", 'timeout' => (int)($config['template_api']['timeout'] ?? 15), 'content' => json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ], ]); $response = @file_get_contents($apiUrl, false, $context); if ($response === false) { senderRespond(['ok' => false, 'error' => 'Template API unreachable'], 502); } $decoded = json_decode($response, true); if (!is_array($decoded) || !empty($decoded['ok']) === false) { senderRespond(['ok' => false, 'error' => 'Template API error', 'detail' => $decoded], 502); } return $decoded; } function sendMail(array $config, string $html, string $subject, array $payload): void { $headers = []; if (!empty($config['mail']['from_email'])) { $fromName = $config['mail']['from_name'] ?? ''; if ($fromName !== '') { $headers[] = 'From: ' . sprintf('"%s" <%s>', addslashes($fromName), $config['mail']['from_email']); } else { $headers[] = 'From: ' . $config['mail']['from_email']; } } $headers[] = 'Content-Type: text/html; charset=utf-8'; $to = $payload['to'] ?? ''; if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) { senderRespond(['ok' => false, 'error' => 'Invalid recipient'], 422); } if (!mail($to, $subject, $html, implode("\r\n", $headers))) { senderRespond(['ok' => false, 'error' => 'mail() transport failed'], 500); } } senderRequireToken($senderConfig); $input = senderInput(); $pdo = senderDb($senderConfig); try { $placeholders = $input['placeholders'] ?? []; if (!is_array($placeholders)) { senderRespond(['ok' => false, 'error' => 'Invalid placeholders'], 422); } $resolved = []; foreach ($placeholders as $key => $definition) { $resolved[$key] = resolvePlaceholderValue($definition, $pdo); } $input['placeholders'] = $resolved; $template = fetchTemplateHtml($senderConfig, $input); if (empty($template['ok']) || empty($template['html'])) { senderRespond(['ok' => false, 'error' => 'Template API returned no HTML'], 502); } $subject = $input['subject'] ?? ($template['subject'] ?? 'EmailTemplate'); sendMail($senderConfig, $template['html'], $subject, $input); senderRespond(['ok' => true]); } catch (Throwable $e) { senderRespond(['ok' => false, 'error' => 'Sender failure', 'detail' => $e->getMessage()], 500); }