Files
wevads-platform/scripts/api_brain-unified-send.php
2026-02-26 04:53:11 +01:00

526 lines
24 KiB
PHP

<?php
/**
* brain-unified-send.php — REAL Send Engine
* Pipeline: Recipient → ISP Detect → Brain Winner → O365/PMTA → Track → Send → Log
*/
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
// DB
try {
$pdo = new PDO('pgsql:host=localhost;dbname=adx_system', 'admin', 'admin123');
$pdo->exec('SET search_path TO admin, public');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo json_encode(['error' => 'DB: ' . $e->getMessage()]); exit;
}
$input = json_decode(file_get_contents('php://input'), true) ?: $_REQUEST;
$action = $input['action'] ?? $_GET['action'] ?? 'send';
// ══════════════ ISP MAP ══════════════
$ISP_MAP = [
't-online.de'=>'T-Online','gmx.de'=>'GMX','gmx.net'=>'GMX','web.de'=>'Web.de',
'outlook.com'=>'Outlook','hotmail.com'=>'Outlook','live.com'=>'Outlook','msn.com'=>'Outlook',
'gmail.com'=>'Gmail','googlemail.com'=>'Gmail',
'yahoo.com'=>'Yahoo','yahoo.fr'=>'Yahoo','yahoo.de'=>'Yahoo',
'ziggo.nl'=>'Ziggo','alice.it'=>'Alice','orange.fr'=>'Orange',
'sfr.fr'=>'SFR','free.fr'=>'Free','laposte.net'=>'LaPoste',
'wanadoo.fr'=>'Orange','aol.com'=>'AOL','icloud.com'=>'iCloud',
'mail.ru'=>'Mail.ru','yandex.ru'=>'Yandex'
];
function detectISP($email, $map) {
$domain = strtolower(explode('@', $email)[1] ?? '');
return $map[$domain] ?? ucfirst(explode('.', $domain)[0]);
}
// ══════════════ TRACKING INJECTION ══════════════
function injectTracking($html, $msgId) {
$trackDomain = 'https://wevup.app';
$tid = urlencode($msgId);
$pixel = '<img src="' . $trackDomain . '/track.php?t=' . $tid . '&e=open" width="1" height="1" style="display:none" alt="">';
if (stripos($html, '</body>') !== false) {
$html = str_ireplace('</body>', $pixel . '</body>', $html);
} else {
$html .= $pixel;
}
$html = preg_replace_callback(
'/href=["\x27]([^"\x27]+)["\x27]/i',
function($m) use ($trackDomain, $tid) {
$url = $m[1];
if (strpos($url, 'mailto:') === 0 || strpos($url, '#') === 0 || strpos($url, $trackDomain) !== false || strpos($url, 'track.php') !== false) return $m[0];
$b64url = rtrim(strtr(base64_encode($url), '+/', '-_'), '=');
return 'href="' . $trackDomain . '/track.php?t=' . $tid . '&e=click&u=' . $b64url . '"';
},
$html
);
return $html;
}
// ══════════════ SEND VIA PMTA (port 25) ══════════════
// ══════════════ PLACEHOLDER REPLACEMENT ══════════════
function replacePlaceholders($html, $offerUrl, $unsubUrl = '', $trackDomain = 'wevup.app') {
// Replace [url] with actual offer landing URL
$html = str_replace('[url]', $offerUrl, $html);
$html = str_replace('[URL]', $offerUrl, $html);
// Replace [unsub] with unsubscribe URL
if ($unsubUrl) {
$html = str_replace('[unsub]', $unsubUrl, $html);
$html = str_replace('[UNSUB]', $unsubUrl, $html);
} else {
$defaultUnsub = 'https://' . $trackDomain . '/track.php?t=unsub_' . time() . '&e=unsub';
$html = str_replace('[unsub]', $defaultUnsub, $html);
$html = str_replace('[UNSUB]', $defaultUnsub, $html);
}
// Replace [domain] with tracking domain
$html = str_replace('[domain]', $trackDomain, $html);
// Replace other common placeholders
$html = str_replace('[CLICK_URL]', $offerUrl, $html);
$html = str_replace('[UNSUB_URL]', $unsubUrl ?: ('https://' . $trackDomain . '/track.php?t=unsub_' . time() . '&e=unsub'), $html);
$html = str_replace('[company]', 'Premium Services', $html);
$html = str_replace('[fname]', 'Valued Customer', $html);
return $html;
}
function sendViaPMTA($from, $to, $subject, $html, $headers = []) {
$msgId = '<' . uniqid('wv_') . '@' . (explode('@', $from)[1] ?? 'wevads.local') . '>';
$boundary = '----=_Part_' . uniqid();
$raw = "From: {$from}\r\n";
$raw .= "To: {$to}\r\n";
$raw .= "Subject: {$subject}\r\n";
$raw .= "Message-ID: {$msgId}\r\n";
$raw .= "Date: " . date('r') . "\r\n";
$raw .= "MIME-Version: 1.0\r\n";
// Custom headers from Brain config
foreach ($headers as $k => $v) {
if (!empty($v) && !in_array(strtolower($k), ['from','to','subject','date','mime-version'])) {
$raw .= "{$k}: {$v}\r\n";
}
}
$raw .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n";
$raw .= "\r\n";
// Plain text
$raw .= "--{$boundary}\r\n";
$raw .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
$raw .= strip_tags($html) . "\r\n";
// HTML
$raw .= "--{$boundary}\r\n";
$raw .= "Content-Type: text/html; charset=UTF-8\r\n\r\n";
$raw .= $html . "\r\n";
$raw .= "--{$boundary}--\r\n";
/* Graph API send */
$jsonFile = tempnam("/tmp", "graph_");
$jsonData = json_encode(["to" => $to, "subject" => $subject, "body" => $html]);
file_put_contents($jsonFile, $jsonData);
$graphResult = trim(shell_exec("python3 /opt/wevads/scripts/graph_send.py " . escapeshellarg($jsonFile) . " 2>&1"));
@unlink($jsonFile);
$ok = (strpos($graphResult, "OK") === 0);
return ["success" => $ok, "method" => "graph-api", "message_id" => $msgId, "smtp_response" => $graphResult];
}
// ══════════════ SEND VIA O365 SMTP ══════════════
function sendViaO365($account, $to, $subject, $html, $headers = []) {
$from = $account['admin_email'];
$pass = $account['admin_password'];
$host = 'smtp.office365.com';
$port = 587;
$msgId = '<' . uniqid('o365_') . '@' . ($account['tenant_domain'] ?? 'outlook.com') . '>';
$boundary = '----=_Part_' . uniqid();
$raw = "From: {$from}\r\n";
$raw .= "To: {$to}\r\n";
$raw .= "Subject: {$subject}\r\n";
$raw .= "Message-ID: {$msgId}\r\n";
$raw .= "Date: " . date('r') . "\r\n";
$raw .= "MIME-Version: 1.0\r\n";
foreach ($headers as $k => $v) {
if (!empty($v) && !in_array(strtolower($k), ['from','to','subject','date','mime-version'])) {
$raw .= "{$k}: {$v}\r\n";
}
}
$raw .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n\r\n";
$raw .= "--{$boundary}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n" . strip_tags($html) . "\r\n";
$raw .= "--{$boundary}\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n" . $html . "\r\n";
$raw .= "--{$boundary}--\r\n";
// SMTP with STARTTLS
$errno = $errstr = '';
$sock = @fsockopen($host, $port, $errno, $errstr, 10);
if (!$sock) return ['success' => false, 'error' => "O365 connect failed: {$errstr}", 'message_id' => $msgId];
fgets($sock, 1024); // 220
fputs($sock, "EHLO wevads.local\r\n");
$ehlo = '';
stream_set_timeout($sock, 5);
while ($line = fgets($sock, 1024)) { $ehlo .= $line; if (substr(trim($line), 3, 1) === ' ') break; }
// STARTTLS
fputs($sock, "STARTTLS\r\n");
$tlsResp = fgets($sock, 1024);
if (strpos($tlsResp, '220') === false) {
fclose($sock);
return ['success' => false, 'error' => 'STARTTLS failed: ' . trim($tlsResp), 'message_id' => $msgId];
}
$crypto = stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
if (!$crypto) {
fclose($sock);
return ['success' => false, 'error' => 'TLS handshake failed', 'message_id' => $msgId];
}
// Re-EHLO after TLS
fputs($sock, "EHLO wevads.local\r\n");
while ($line = fgets($sock, 1024)) { if (substr(trim($line), 3, 1) === ' ') break; }
// AUTH LOGIN
fputs($sock, "AUTH LOGIN\r\n");
fgets($sock, 1024);
fputs($sock, base64_encode($from) . "\r\n");
fgets($sock, 1024);
fputs($sock, base64_encode($pass) . "\r\n");
$authResp = fgets($sock, 1024);
if (strpos($authResp, '235') === false) {
fputs($sock, "QUIT\r\n"); fclose($sock);
return ['success' => false, 'error' => 'AUTH failed: ' . trim($authResp), 'message_id' => $msgId, 'account' => $from];
}
fputs($sock, "MAIL FROM:<{$from}>\r\n"); fgets($sock, 1024);
fputs($sock, "RCPT TO:<{$to}>\r\n"); fgets($sock, 1024);
fputs($sock, "DATA\r\n"); fgets($sock, 1024);
fputs($sock, $raw . "\r\n.\r\n");
$sendResp = fgets($sock, 1024);
fputs($sock, "QUIT\r\n"); fclose($sock);
$ok = (strpos($sendResp, '250') !== false);
return ['success' => $ok, 'method' => 'o365', 'message_id' => $msgId, 'account' => $from, 'smtp_response' => trim($sendResp)];
}
// ══════════════ ACTIONS ══════════════
switch ($action) {
case 'send':
$to = $input['to_email'] ?? $input['to'] ?? '';
$from = $input['from_email'] ?? $input['from'] ?? '';
$subject = $input['subject'] ?? '';
$bodyHtml = $input['body_html'] ?? $input['body'] ?? '';
$configId = $input['config_id'] ?? null;
$method = $input['method'] ?? null; // o365 or pmta, auto if null
if (empty($to) || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
echo json_encode(['error' => 'Valid to_email required']); exit;
}
if (empty($subject) || empty($bodyHtml)) {
echo json_encode(['error' => 'Subject and body required']); exit;
}
// 1. Detect ISP
$isp = detectISP($to, $ISP_MAP);
// 2. Pick winner config
$config = null;
if ($configId) {
$stmt = $pdo->prepare("SELECT * FROM brain_send_configs WHERE id = ?");
$stmt->execute([$configId]);
$config = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (!$config) {
$stmt = $pdo->prepare("SELECT * FROM brain_send_configs WHERE (isp_target ILIKE ? OR isp_target ILIKE ?) AND status = 'winner' ORDER BY inbox_rate DESC LIMIT 1");
$domain = strtolower(explode('@', $to)[1] ?? '');
$stmt->execute(["%{$isp}%", "%{$domain}%"]);
$config = $stmt->fetch(PDO::FETCH_ASSOC);
}
// Fallback: any winner
if (!$config) {
$config = $pdo->query("SELECT * FROM brain_send_configs WHERE status = 'winner' ORDER BY inbox_rate DESC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
}
$sendMethod = $method ?? ($config['send_method'] ?? 'pmta');
// 3. Build headers from config
$customHeaders = [];
if (!empty($config['x_mailer'])) $customHeaders['X-Mailer'] = $config['x_mailer'];
if (!empty($config['priority'])) $customHeaders['X-Priority'] = $config['priority'];
if (!empty($config['custom_headers'])) {
$extra = is_string($config['custom_headers']) ? json_decode($config['custom_headers'], true) : $config['custom_headers'];
if (is_array($extra)) $customHeaders = array_merge($customHeaders, $extra);
}
// 4. Generate message ID for tracking
$trackId = uniqid('wv') . '_' . substr(md5($to . time()), 0, 6);
// 4b. Replace placeholders in body
$offerUrl = $input['offer_url'] ?? '';
if (empty($offerUrl)) {
$offerId = $input['offer_id'] ?? null;
if ($offerId) {
$ofStmt = $pdo->prepare("SELECT offer_url FROM affiliate_offers WHERE id = ?");
$ofStmt->execute([$offerId]);
$ofRow = $ofStmt->fetch(PDO::FETCH_ASSOC);
if ($ofRow) $offerUrl = $ofRow['offer_url'];
}
}
$trackDomain = 'https://wevup.app';
$unsubUrl = $trackDomain . '/track.php?t=' . urlencode($trackId) . '&e=unsub';
$openPixelUrl = $trackDomain . '/track.php?t=' . urlencode($trackId) . '&e=open';
$bodyHtml = str_replace('[url]', $offerUrl, $bodyHtml);
$bodyHtml = str_replace('[unsub]', $unsubUrl, $bodyHtml);
$bodyHtml = str_replace('[open]', $openPixelUrl, $bodyHtml);
$bodyHtml = str_replace('[subject]', $subject, $bodyHtml);
$bodyHtml = str_replace('[email]', $to, $bodyHtml);
$bodyHtml = str_replace('[track_id]', $trackId, $bodyHtml);
$bodyHtml = str_replace('{offer_link}', $offerUrl, $bodyHtml);
$bodyHtml = str_replace('{unsubscribe}', $unsubUrl, $bodyHtml);
$bodyHtml = str_replace('{tracking}', $openPixelUrl, $bodyHtml);
// 5. Inject tracking
$bodyHtml = injectTracking($bodyHtml, $trackId);
// 6. Determine from address
$o365Account = null;
if ($sendMethod === 'o365') {
if (empty($from)) {
// Pick least-used active O365 account
$stmt = $pdo->query("SELECT id, admin_email, admin_password, tenant_domain FROM office_accounts WHERE status = 'active' AND admin_password IS NOT NULL AND admin_password != '' ORDER BY RANDOM() LIMIT 1");
$o365Account = $stmt->fetch(PDO::FETCH_ASSOC);
if ($o365Account) $from = $o365Account['admin_email'];
} else {
// Find the specific O365 account
$stmt = $pdo->prepare("SELECT id, admin_email, admin_password, tenant_domain FROM office_accounts WHERE admin_email = ? AND status = 'active'");
$stmt->execute([$from]);
$o365Account = $stmt->fetch(PDO::FETCH_ASSOC);
}
}
if (empty($from)) {
$from = 'info@' . ($config['from_domain'] ?? 'wevup.app');
}
// 7. SEND
$result = null;
if ($sendMethod === 'o365' && $o365Account) {
$result = sendViaO365($o365Account, $to, $subject, $bodyHtml, $customHeaders);
} else {
$result = sendViaPMTA($from, $to, $subject, $bodyHtml, $customHeaders);
}
// 8. Log
try {
$pdo->prepare("INSERT INTO unified_send_log_new (config_id, send_method, isp_target, from_email, to_email, subject, message_id, status, error_message, headers_used, response_data, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,NOW())")
->execute([
$config['id'] ?? null,
$result['method'] ?? $sendMethod,
$isp,
$from,
$to,
$subject,
$result['message_id'] ?? $trackId,
$result['success'] ? 'sent' : 'failed',
$result['error'] ?? null,
json_encode($customHeaders),
json_encode($result)
]);
} catch (Exception $e) { /* log error silently */ }
echo json_encode([
'success' => $result['success'],
'method' => $result['method'] ?? $sendMethod,
'isp' => $isp,
'from' => $from,
'to' => $to,
'config_id' => $config['id'] ?? null,
'config_inbox_rate' => $config['inbox_rate'] ?? null,
'message_id' => $result['message_id'] ?? null,
'track_id' => $trackId,
'smtp_response' => $result['smtp_response'] ?? null,
'error' => $result['error'] ?? null,
'timestamp' => date('c')
]);
break;
case 'detect_isp':
$email = $input['email'] ?? $_GET['email'] ?? '';
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo json_encode(['error' => 'Valid email required']); exit;
}
$domain = strtolower(explode('@', $email)[1]);
$isp = $ISP_MAP[$domain] ?? ucfirst(explode('.', $domain)[0]);
$stmt = $pdo->prepare("SELECT id, send_method, inbox_rate, x_mailer, from_domain, smtp_host FROM brain_send_configs WHERE (isp_target ILIKE ? OR isp_target ILIKE ?) AND status='winner' ORDER BY inbox_rate DESC LIMIT 5");
$stmt->execute(["%{$isp}%", "%{$domain}%"]);
$winners = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['email'=>$email, 'domain'=>$domain, 'isp'=>$isp, 'winners'=>$winners, 'count'=>count($winners)]);
break;
case 'stats':
try {
$stats = $pdo->query("SELECT COUNT(*) as total, COUNT(*) FILTER (WHERE created_at >= CURRENT_DATE) as today, COUNT(*) FILTER (WHERE status='sent') as success, COUNT(*) FILTER (WHERE status='failed') as failed FROM unified_send_log_new")->fetch(PDO::FETCH_ASSOC);
$byIsp = $pdo->query("SELECT isp_target, COUNT(*) as cnt, COUNT(*) FILTER (WHERE status='sent') as ok FROM unified_send_log_new WHERE created_at >= CURRENT_DATE GROUP BY isp_target ORDER BY cnt DESC LIMIT 10")->fetchAll(PDO::FETCH_ASSOC);
$byMethod = $pdo->query("SELECT send_method, COUNT(*) as cnt FROM unified_send_log_new WHERE created_at >= CURRENT_DATE GROUP BY send_method")->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
$stats = ['total'=>0,'today'=>0,'success'=>0,'failed'=>0];
$byIsp = []; $byMethod = [];
}
echo json_encode(['stats'=>$stats, 'by_isp'=>$byIsp, 'by_method'=>$byMethod, 'timestamp'=>date('c')]);
break;
case 'test_send':
// Dry run — don't actually send, just show what would happen
$to = $input['to_email'] ?? $input['to'] ?? '';
if (empty($to)) { echo json_encode(['error' => 'to_email required']); exit; }
$isp = detectISP($to, $ISP_MAP);
$domain = strtolower(explode('@', $to)[1] ?? '');
$stmt = $pdo->prepare("SELECT id, send_method, inbox_rate, x_mailer, from_domain FROM brain_send_configs WHERE (isp_target ILIKE ? OR isp_target ILIKE ?) AND status='winner' ORDER BY inbox_rate DESC LIMIT 3");
$stmt->execute(["%{$isp}%", "%{$domain}%"]);
$winners = $stmt->fetchAll(PDO::FETCH_ASSOC);
$o365count = $pdo->query("SELECT COUNT(*) FROM office_accounts WHERE status='active' AND admin_password IS NOT NULL AND admin_password != ''")->fetchColumn();
$pmtaOk = false;
$sock = @fsockopen('127.0.0.1', 25, $en, $es, 2);
if ($sock) { fgets($sock, 256); $pmtaOk = true; fclose($sock); }
echo json_encode([
'dry_run' => true,
'to' => $to, 'isp' => $isp, 'domain' => $domain,
'winners' => $winners,
'recommended_method' => !empty($winners) ? $winners[0]['send_method'] : 'pmta',
'recommended_config' => !empty($winners) ? $winners[0] : null,
'o365_available' => (int)$o365count,
'pmta_online' => $pmtaOk,
'tracking_domain' => 'wevup.app'
]);
break;
case 'get_winners':
$isp = $input['isp'] ?? $_GET['isp'] ?? 'all';
if ($isp === 'all') {
$winners = $pdo->query("SELECT * FROM brain_send_configs WHERE status='winner' ORDER BY inbox_rate DESC")->fetchAll(PDO::FETCH_ASSOC);
} else {
$stmt = $pdo->prepare("SELECT * FROM brain_send_configs WHERE isp_target ILIKE ? AND status='winner' ORDER BY inbox_rate DESC");
$stmt->execute(["%{$isp}%"]);
$winners = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
echo json_encode(['isp'=>$isp, 'configs'=>$winners, 'count'=>count($winners)]);
break;
default:
case 'send_offer':
$to = $input['to_email'] ?? $input['to'] ?? '';
$creativeId = $input['creative_id'] ?? null;
$method = $input['method'] ?? null;
if (empty($to) || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
echo json_encode(['error' => 'Valid to_email required']); exit;
}
// Fetch creative
if ($creativeId) {
$stmt = $pdo->prepare("SELECT oc.*, ao.name as offer_name FROM offer_creatives oc LEFT JOIN affiliate.offers ao ON oc.offer_id = ao.id WHERE oc.id = ?");
$stmt->execute([$creativeId]);
} else {
$stmt = $pdo->query("SELECT oc.*, ao.name as offer_name FROM offer_creatives oc LEFT JOIN affiliate.offers ao ON oc.offer_id = ao.id WHERE oc.status = 'active' AND oc.html_body IS NOT NULL AND oc.html_body != '' ORDER BY RANDOM() LIMIT 1");
}
$creative = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$creative) { echo json_encode(['error' => 'No creative found']); exit; }
$landingUrl = $creative['landing_url'] ?: 'https://google.com';
$trackDomain = 'wevup.app';
$unsubUrl = "https://{$trackDomain}/track.php?t=unsub_" . urlencode($to) . "&e=unsub";
// 1. Replace placeholders in creative body
$bodyHtml = replacePlaceholders($creative['html_body'], $landingUrl, $unsubUrl, $trackDomain);
$subject = $creative['subject_line'] ?: 'Special Offer';
$fromName = $creative['from_name'] ?: 'Member Services';
// 2. Detect ISP
$isp = detectISP($to, $ISP_MAP);
// 3. Pick winner config for this ISP
$domain = strtolower(explode('@', $to)[1] ?? '');
$stmt2 = $pdo->prepare("SELECT * FROM brain_send_configs WHERE (isp_target ILIKE ? OR isp_target ILIKE ?) AND status = 'winner' ORDER BY inbox_rate DESC LIMIT 1");
$stmt2->execute(["%{$isp}%", "%{$domain}%"]);
$config = $stmt2->fetch(PDO::FETCH_ASSOC);
if (!$config) {
$config = $pdo->query("SELECT * FROM brain_send_configs WHERE status = 'winner' ORDER BY inbox_rate DESC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
}
$sendMethod = $method ?? ($config['send_method'] ?? 'pmta');
// 4. Build headers from config
$customHeaders = [];
if (!empty($config['x_mailer'])) $customHeaders['X-Mailer'] = $config['x_mailer'];
if (!empty($config['custom_headers'])) {
$extra = is_string($config['custom_headers']) ? json_decode($config['custom_headers'], true) : $config['custom_headers'];
if (is_array($extra)) $customHeaders = array_merge($customHeaders, $extra);
}
$customHeaders['List-Unsubscribe'] = "<{$unsubUrl}>";
// 5. Generate tracking ID & inject tracking
$trackId = 'wv' . uniqid() . '_' . substr(md5($to . time()), 0, 6);
$bodyHtml = injectTracking($bodyHtml, $trackId);
// 6. Determine from address
$from = '';
$o365Account = null;
if ($sendMethod === 'o365') {
$stmt3 = $pdo->query("SELECT id, admin_email, admin_password, tenant_domain FROM office_accounts WHERE status = 'active' AND admin_password IS NOT NULL AND admin_password != '' ORDER BY RANDOM() LIMIT 1");
$o365Account = $stmt3->fetch(PDO::FETCH_ASSOC);
if ($o365Account) $from = $o365Account['admin_email'];
}
if (empty($from)) $from = 'info@' . ($config['from_domain'] ?? $trackDomain);
// 7. SEND
if ($sendMethod === 'o365' && $o365Account) {
$result = sendViaO365($o365Account, $to, $subject, $bodyHtml, $customHeaders);
} else {
$result = sendViaPMTA($from, $to, $subject, $bodyHtml, $customHeaders);
}
// 8. Log to unified_send_log_new
try {
$pdo->prepare("INSERT INTO unified_send_log_new (config_id, send_method, isp_target, from_email, to_email, subject, message_id, status, error_message, headers_used, response_data, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,NOW())")
->execute([
$config['id'] ?? null,
$result['method'] ?? $sendMethod,
$isp, $from, $to, $subject,
$result['message_id'] ?? $trackId,
$result['success'] ? 'sent' : 'failed',
$result['success'] ? null : ($result['smtp_response'] ?? 'unknown'),
json_encode($customHeaders),
json_encode($result)
]);
} catch (Exception $e) {}
// 9. Update creative stats
try {
$pdo->prepare("UPDATE offer_creatives SET times_used = COALESCE(times_used,0) + 1, last_used = NOW() WHERE id = ?")->execute([$creative['id']]);
} catch (Exception $e) {}
echo json_encode([
'success' => $result['success'] ?? false,
'creative_id' => $creative['id'],
'offer_id' => $creative['offer_id'],
'offer_name' => $creative['offer_name'] ?? '',
'subject' => $subject,
'to' => $to,
'from' => $from,
'isp' => $isp,
'method' => $result['method'] ?? $sendMethod,
'tracking_id' => $trackId,
'landing_url' => $landingUrl,
'message_id' => $result['message_id'] ?? $trackId,
'smtp_response' => $result['smtp_response'] ?? ''
]);
break;
echo json_encode(['status'=>'online','service'=>'Brain Unified Send Engine','version'=>'3.0','actions'=>['send','send_offer','detect_isp','stats','test_send','get_winners']]);
}