| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- <?php
- /**
- * waitlist.php — Waitlist signup endpoint
- * ----------------------------------------
- * Accepts POST with JSON { "email": "...", "source": "..." }
- * Validates the email, stores to telemetry DB (existing), and
- * sends a notification email via PHPMailer + SMTP.
- *
- * Environment variables (set in docker-compose.yml):
- * SMTP_HOST — e.g. smtp.gmail.com or smtp.sendgrid.net
- * SMTP_PORT — 587 (TLS) or 465 (SSL), default 587
- * SMTP_USER — SMTP username / email address
- * SMTP_PASS — SMTP password or app password
- * SMTP_FROM — From address, default: SMTP_USER
- * SMTP_FROM_NAME — From name, default: "Tas Planning Assistant"
- * NOTIFY_EMAIL — Where to send admin notifications (your inbox)
- * APP_API_BASE — API base for telemetry fallback
- */
- declare(strict_types=1);
- header('Content-Type: application/json; charset=utf-8');
- header('Access-Control-Allow-Methods: POST, OPTIONS');
- header('Access-Control-Allow-Headers: Content-Type');
- // Handle preflight
- if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
- http_response_code(204);
- exit;
- }
- if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
- http_response_code(405);
- echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
- exit;
- }
- require_once __DIR__ . '/vendor/autoload.php';
- use PHPMailer\PHPMailer\PHPMailer;
- use PHPMailer\PHPMailer\SMTP;
- use PHPMailer\PHPMailer\Exception as MailException;
- // ── Input ──────────────────────────────────────────────────────────────
- $raw = file_get_contents('php://input') ?: '{}';
- $input = json_decode($raw, true) ?: [];
- $email = trim((string)($input['email'] ?? $_POST['email'] ?? ''));
- $source = trim((string)($input['source'] ?? $_POST['source'] ?? 'waitlist'));
- $name = trim((string)($input['name'] ?? $_POST['name'] ?? ''));
- // ── Validate ───────────────────────────────────────────────────────────
- if (!$email) {
- http_response_code(400);
- echo json_encode(['ok' => false, 'error' => 'Email address is required']);
- exit;
- }
- if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
- http_response_code(422);
- echo json_encode(['ok' => false, 'error' => 'Please enter a valid email address']);
- exit;
- }
- // Basic honeypot / rate limit: reject obviously bad inputs
- if (strlen($email) > 254 || preg_match('/[<>"\']/', $email)) {
- http_response_code(422);
- echo json_encode(['ok' => false, 'error' => 'Invalid email address']);
- exit;
- }
- // ── SMTP config from environment ───────────────────────────────────────
- $smtpHost = $_SERVER['SMTP_HOST'] ?? $_ENV['SMTP_HOST'] ?? getenv('SMTP_HOST') ?: '';
- $smtpPort = (int)($_SERVER['SMTP_PORT'] ?? $_ENV['SMTP_PORT'] ?? getenv('SMTP_PORT') ?: 587);
- $smtpUser = $_SERVER['SMTP_USER'] ?? $_ENV['SMTP_USER'] ?? getenv('SMTP_USER') ?: '';
- $smtpPass = $_SERVER['SMTP_PASS'] ?? $_ENV['SMTP_PASS'] ?? getenv('SMTP_PASS') ?: '';
- $smtpFrom = $_SERVER['SMTP_FROM'] ?? $_ENV['SMTP_FROM'] ?? getenv('SMTP_FROM') ?: $smtpUser;
- $smtpFromName = $_SERVER['SMTP_FROM_NAME'] ?? $_ENV['SMTP_FROM_NAME'] ?? getenv('SMTP_FROM_NAME') ?: 'Tas Planning Assistant';
- $notifyEmail = $_SERVER['NOTIFY_EMAIL'] ?? $_ENV['NOTIFY_EMAIL'] ?? getenv('NOTIFY_EMAIL') ?: $smtpUser;
- $errors = [];
- // ── 1. Send confirmation email to subscriber ───────────────────────────
- if ($smtpHost && $smtpUser) {
- try {
- $mail = new PHPMailer(true);
- $mail->isSMTP();
- $mail->Host = $smtpHost;
- $mail->SMTPAuth = true;
- $mail->Username = $smtpUser;
- $mail->Password = $smtpPass;
- $mail->SMTPSecure = $smtpPort === 465 ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
- $mail->Port = $smtpPort;
- $mail->CharSet = 'UTF-8';
- $mail->setFrom($smtpFrom, $smtpFromName);
- $mail->addAddress($email, $name ?: '');
- $mail->addReplyTo($smtpFrom, $smtpFromName);
- $mail->isHTML(true);
- $mail->Subject = "You're on the waitlist — Tasmania's AI Planning Scheme Pro";
- $mail->Body = confirmationHtml($email, $name);
- $mail->AltBody = confirmationText($email, $name);
- $mail->send();
- } catch (MailException $e) {
- // Log but don't fail the whole request — notification is non-critical
- $errors[] = 'confirmation_mail: ' . $mail->ErrorInfo;
- error_log('[waitlist] Confirmation mail failed: ' . $mail->ErrorInfo);
- }
- // ── 2. Send admin notification ─────────────────────────────────────
- if ($notifyEmail) {
- try {
- $notify = new PHPMailer(true);
- $notify->isSMTP();
- $notify->Host = $smtpHost;
- $notify->SMTPAuth = true;
- $notify->Username = $smtpUser;
- $notify->Password = $smtpPass;
- $notify->SMTPSecure = $smtpPort === 465 ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
- $notify->Port = $smtpPort;
- $notify->CharSet = 'UTF-8';
- $notify->setFrom($smtpFrom, $smtpFromName);
- $notify->addAddress($notifyEmail);
- $notify->Subject = "New waitlist signup: {$email}";
- $notify->Body = "<p><strong>Email:</strong> {$email}</p>"
- . "<p><strong>Source:</strong> {$source}</p>"
- . "<p><strong>Time:</strong> " . date('d M Y H:i:s T') . "</p>";
- $notify->AltBody = "New waitlist signup\nEmail: {$email}\nSource: {$source}\nTime: " . date('d M Y H:i:s T');
- $notify->isHTML(true);
- $notify->send();
- } catch (MailException $e) {
- $errors[] = 'notify_mail: ' . $notify->ErrorInfo;
- error_log('[waitlist] Notify mail failed: ' . $notify->ErrorInfo);
- }
- }
- } else {
- // No SMTP configured — log a warning but still return success
- error_log('[waitlist] SMTP not configured. Set SMTP_HOST and SMTP_USER in environment.');
- $errors[] = 'smtp_not_configured';
- }
- // ── 3. Log to telemetry DB via the API (best-effort) ───────────────────
- $apiBase = $_SERVER['APP_API_BASE'] ?? $_ENV['APP_API_BASE'] ?? getenv('APP_API_BASE') ?: 'https://api.modulos.com.au/ask';
- $telUrl = preg_replace('/\/ask\/?$/', '', $apiBase) . '/telemetry';
- $telPayload = json_encode([
- 'type' => 'waitlist_join',
- 'email' => $email,
- 'source'=> $source,
- 'ts' => date('c'),
- ]);
- // Fire-and-forget with a short timeout
- $ctx = stream_context_create(['http' => [
- 'method' => 'POST',
- 'header' => "Content-Type: application/json\r\n",
- 'content' => $telPayload,
- 'timeout' => 2,
- 'ignore_errors' => true,
- ]]);
- @file_get_contents($telUrl, false, $ctx);
- // ── Response ───────────────────────────────────────────────────────────
- echo json_encode([
- 'ok' => true,
- 'message' => "You're on the list! We'll be in touch when Pro launches.",
- 'warnings'=> $errors ?: null,
- ]);
- // ── Email templates ────────────────────────────────────────────────────
- function confirmationHtml(string $email, string $name = ''): string {
- $greeting = $name ? "Hi {$name}," : 'Hi there,';
- return <<<HTML
- <!DOCTYPE html>
- <html>
- <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
- <body style="margin:0;padding:0;background:#f4f4f4;font-family:'Segoe UI',Arial,sans-serif;">
- <table width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f4;padding:32px 16px;">
- <tr><td align="center">
- <table width="580" cellpadding="0" cellspacing="0" style="background:#0b0f0e;border-radius:12px;overflow:hidden;max-width:580px;">
- <!-- Header -->
- <tr>
- <td style="background:#141a17;padding:28px 32px;border-bottom:1px solid rgba(255,255,255,0.07);">
- <table cellpadding="0" cellspacing="0">
- <tr>
- <td style="background:rgba(45,220,138,0.1);border:1px solid rgba(45,220,138,0.25);border-radius:6px;width:28px;height:28px;text-align:center;vertical-align:middle;">
- <span style="color:#2ddc8a;font-size:14px;font-weight:bold;">▲</span>
- </td>
- <td style="padding-left:10px;color:#eaf0ec;font-size:14px;font-weight:500;">
- Tasmanian Planning Scheme
- </td>
- </tr>
- </table>
- </td>
- </tr>
- <!-- Body -->
- <tr>
- <td style="padding:36px 32px;">
- <h1 style="color:#eaf0ec;font-size:22px;font-weight:400;margin:0 0 16px;line-height:1.3;">
- You're on the <span style="color:#2ddc8a;font-style:italic;">Pro waitlist</span>
- </h1>
- <p style="color:#8fa899;font-size:15px;line-height:1.7;margin:0 0 16px;">
- {$greeting}
- </p>
- <p style="color:#8fa899;font-size:15px;line-height:1.7;margin:0 0 24px;">
- Thanks for joining the waitlist for <strong style="color:#eaf0ec;">Tasmanian Planning Scheme Pro</strong>.
- We'll email you as soon as it's available — including early-access pricing for waitlist members.
- </p>
- <!-- Feature list -->
- <table cellpadding="0" cellspacing="0" style="background:#141a17;border:1px solid rgba(255,255,255,0.07);border-radius:10px;width:100%;margin-bottom:28px;">
- <tr><td style="padding:20px 24px;">
- <p style="color:#4f6459;font-size:11px;font-weight:500;letter-spacing:0.1em;text-transform:uppercase;margin:0 0 14px;">What's included in Pro</p>
- <table cellpadding="0" cellspacing="0" width="100%">
- <tr><td style="padding:5px 0;color:#8fa899;font-size:13px;">✓ Full planning report from one brief</td></tr>
- <tr><td style="padding:5px 0;color:#8fa899;font-size:13px;">✓ Zone + codes tables with A/P assessment</td></tr>
- <tr><td style="padding:5px 0;color:#8fa899;font-size:13px;">✓ Clause-linked sources throughout</td></tr>
- <tr><td style="padding:5px 0;color:#8fa899;font-size:13px;">✓ Google Docs export</td></tr>
- <tr><td style="padding:5px 0;color:#8fa899;font-size:13px;">✓ NCC/AS hooks when released</td></tr>
- </table>
- </td></tr>
- </table>
- <p style="color:#8fa899;font-size:14px;line-height:1.7;margin:0 0 28px;">
- In the meantime, the free assistant is available at
- <a href="https://tasplanning.report" style="color:#2ddc8a;text-decoration:none;">tasplanning.report</a>
- — ask questions about zones, overlays, setbacks, and acceptable solutions with full clause citations.
- </p>
- <a href="https://tasplanning.report/local_state-planning-scheme.php"
- style="display:inline-block;background:#2ddc8a;color:#0b0f0e;font-size:14px;font-weight:500;
- padding:12px 24px;border-radius:8px;text-decoration:none;">
- Try the free assistant →
- </a>
- </td>
- </tr>
- <!-- Footer -->
- <tr>
- <td style="padding:20px 32px;border-top:1px solid rgba(255,255,255,0.07);">
- <p style="color:#4f6459;font-size:12px;margin:0;line-height:1.6;">
- You're receiving this because you signed up at tasplanning.report.<br>
- To unsubscribe, reply to this email with "unsubscribe" in the subject.
- </p>
- </td>
- </tr>
- </table>
- </td></tr>
- </table>
- </body>
- </html>
- HTML;
- }
- function confirmationText(string $email, string $name = ''): string {
- $greeting = $name ? "Hi {$name}," : 'Hi there,';
- return <<<TEXT
- {$greeting}
- You're on the waitlist for Tasmanian Planning Scheme Pro.
- We'll email you as soon as it's available — including early-access pricing for waitlist members.
- What's included in Pro:
- - Full planning report from one brief
- - Zone + codes tables with A/P assessment
- - Clause-linked sources throughout
- - Google Docs export
- - NCC/AS hooks when released
- In the meantime, the free assistant is available at https://tasplanning.report
- ---
- You're receiving this because you signed up at tasplanning.report.
- To unsubscribe, reply with "unsubscribe" in the subject.
- TEXT;
- }
|