$ok, 'message' => $message], JSON_UNESCAPED_SLASHES); exit; } function loadContactEnvironment(): array { $values = []; foreach (['/etc/desktop-resume/contact.env', '/vagrant/.env'] as $path) { if (!is_readable($path)) { continue; } foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) { $line = trim($line); if ($line === '' || str_starts_with($line, '#') || !str_contains($line, '=')) { continue; } [$key, $value] = array_map('trim', explode('=', $line, 2)); $values[$key] = trim($value, "\"'"); } } return $values; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { respond(405, false, 'Please submit the contact form.'); } $contentType = $_SERVER['CONTENT_TYPE'] ?? ''; if (str_contains($contentType, 'application/json')) { $input = json_decode(file_get_contents('php://input') ?: '{}', true); $input = is_array($input) ? $input : []; } else { $input = $_POST; } if (trim((string) ($input['website'] ?? '')) !== '') { respond(200, true, 'Thanks—your ticket is in the queue.'); } $startedAt = (int) ($input['started_at'] ?? 0); if ($startedAt <= 0 || time() - $startedAt < 3 || time() - $startedAt > 86400) { respond(400, false, 'Please refresh the page and try again.'); } $name = trim((string) ($input['name'] ?? '')); $email = trim((string) ($input['email'] ?? '')); $topic = trim((string) ($input['topic'] ?? '')); $message = trim((string) ($input['message'] ?? '')); $allowedTopics = ['Junior support opportunity', 'Interview conversation', 'Networking', 'Website feedback']; if ($name === '' || mb_strlen($name) > 80) { respond(422, false, 'Please enter your name.'); } if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email) > 160) { respond(422, false, 'Please enter a valid email address.'); } if (!in_array($topic, $allowedTopics, true)) { respond(422, false, 'Please choose a ticket type.'); } if (mb_strlen($message) < 20 || mb_strlen($message) > 3000) { respond(422, false, 'Please include a little more detail (20–3,000 characters).'); } $rateKey = hash('sha256', ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '|desktop-contact'); $rateFile = sys_get_temp_dir() . '/contact-' . $rateKey; $lastSent = is_file($rateFile) ? (int) file_get_contents($rateFile) : 0; if ($lastSent > time() - 60) { respond(429, false, 'That ticket just came through. Please wait a minute before sending another.'); } $environment = loadContactEnvironment(); $apiKey = getenv('RESEND_API_KEY') ?: ($environment['RESEND_API_KEY'] ?? $environment['RESEND_KEY'] ?? ''); $from = getenv('RESEND_FROM') ?: ($environment['RESEND_FROM'] ?? 'Desktop Support Portfolio '); if ($apiKey === '') { error_log('Contact form: RESEND_API_KEY is not configured.'); respond(503, false, 'The message desk is temporarily offline. Please email me directly instead.'); } $safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $safeTopic = htmlspecialchars($topic, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $safeMessage = nl2br(htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')); $ticket = 'WEB-' . strtoupper(substr(hash('sha256', $email . microtime()), 0, 8)); $payload = [ 'from' => $from, 'to' => ['praesidium909@gmail.com'], 'reply_to' => $email, 'subject' => "[$ticket] $topic — $name", 'html' => "

New portfolio support ticket · $ticket

$safeTopic

From$safeName
Email$safeEmail
$safeMessage

Reply directly to this email to respond.

", 'text' => "Ticket: $ticket\nType: $topic\nFrom: $name <$email>\n\n$message", ]; $curl = curl_init('https://api.resend.com/emails'); curl_setopt_array($curl, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES), ]); $response = curl_exec($curl); $status = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE); $error = curl_error($curl); curl_close($curl); if ($response === false || $status < 200 || $status >= 300) { error_log("Contact form: Resend failed with HTTP $status: $error"); respond(502, false, 'The ticket could not be sent right now. Please use the email link instead.'); } file_put_contents($rateFile, (string) time(), LOCK_EX); respond(200, true, "Ticket $ticket received. I’ll be in touch soon!");