/i', "\n", $html)); } // Fall back to PHP mail() if SMTP is not configured if ($smtp_host === '') { $headers = "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: text/html; charset=UTF-8\r\n"; if ($smtp_from !== '') { $headers .= 'From: ' . _mail_encode_name($smtp_from_name) . ' <' . $smtp_from . ">\r\n"; } return @mail($to_email, $subject, $html, $headers); } try { return _smtp_send($smtp_host, $smtp_port, $smtp_user, $smtp_pass, $smtp_from, $smtp_from_name, $to_email, $to_name, $subject, $html, $text); } catch (Throwable $e) { error_log('Mailer error: ' . $e->getMessage()); return false; } } /** * Return a simple styled HTML email wrapper. */ function email_template(string $title, string $body_html): string { $site_name = get_setting('site_name', 'Rosary Presenter'); return << {$title}

✝ {$site_name}

{$body_html}
© {$site_name}. This email was sent automatically.
HTML; } // --------------------------------------------------------------------------- // Internal SMTP helpers // --------------------------------------------------------------------------- function _smtp_send( string $host, int $port, string $user, string $pass, string $from_addr, string $from_name, string $to_addr, string $to_name, string $subject, string $html, string $text ): bool { $use_ssl = ($port === 465); $socket_addr = ($use_ssl ? 'ssl://' : 'tcp://') . $host . ':' . $port; $errno = 0; $errstr = ''; $sock = stream_socket_client($socket_addr, $errno, $errstr, 15); if (!$sock) { throw new RuntimeException("SMTP connect failed ({$errno}): {$errstr}"); } stream_set_timeout($sock, 15); _smtp_expect($sock, 220); _smtp_cmd($sock, 'EHLO ' . gethostname()); $ehlo = _smtp_read_all($sock); // STARTTLS for port 587 if (!$use_ssl && strpos($ehlo, 'STARTTLS') !== false) { _smtp_cmd($sock, 'STARTTLS'); _smtp_expect($sock, 220); if (!stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { throw new RuntimeException('STARTTLS negotiation failed'); } _smtp_cmd($sock, 'EHLO ' . gethostname()); _smtp_read_all($sock); } // AUTH LOGIN if ($user !== '') { _smtp_cmd($sock, 'AUTH LOGIN'); _smtp_expect($sock, 334); _smtp_cmd($sock, base64_encode($user)); _smtp_expect($sock, 334); _smtp_cmd($sock, base64_encode($pass)); _smtp_expect($sock, 235); } _smtp_cmd($sock, 'MAIL FROM:<' . $from_addr . '>'); _smtp_expect($sock, 250); _smtp_cmd($sock, 'RCPT TO:<' . $to_addr . '>'); _smtp_expect($sock, [250, 251]); _smtp_cmd($sock, 'DATA'); _smtp_expect($sock, 354); $boundary = 'b_' . bin2hex(random_bytes(8)); $date = date('r'); $msg_id = bin2hex(random_bytes(12)) . '@' . gethostname(); $headers = "Date: {$date}\r\n"; $headers .= 'From: ' . _mail_encode_name($from_name) . ' <' . $from_addr . ">\r\n"; $headers .= 'To: ' . _mail_encode_name($to_name) . ' <' . $to_addr . ">\r\n"; $headers .= 'Subject: ' . _mail_encode_name($subject) . "\r\n"; $headers .= "Message-ID: <{$msg_id}>\r\n"; $headers .= "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n"; $body = "--{$boundary}\r\n"; $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; $body .= "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; $body .= quoted_printable_encode($text) . "\r\n"; $body .= "--{$boundary}\r\n"; $body .= "Content-Type: text/html; charset=UTF-8\r\n"; $body .= "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; $body .= quoted_printable_encode($html) . "\r\n"; $body .= "--{$boundary}--\r\n"; // Dot-stuff the body $body = preg_replace('/^\.$/m', '..', $body); fwrite($sock, $headers . "\r\n" . $body . "\r\n.\r\n"); _smtp_expect($sock, 250); _smtp_cmd($sock, 'QUIT'); fclose($sock); return true; } function _smtp_cmd($sock, string $cmd): void { fwrite($sock, $cmd . "\r\n"); } function _smtp_read_all($sock): string { $data = ''; while (!feof($sock)) { $line = fgets($sock, 512); if ($line === false) break; $data .= $line; // Last line of multi-line response has a space after the code if (strlen($line) >= 4 && $line[3] === ' ') break; } return $data; } function _smtp_expect($sock, $codes): void { $response = _smtp_read_all($sock); $code = (int)substr(trim($response), 0, 3); $expected = (array)$codes; if (!in_array($code, $expected, true)) { throw new RuntimeException("SMTP unexpected response {$code}: " . trim($response)); } } function _mail_encode_name(string $name): string { if ($name === '') return ''; // RFC 2047 encode if needed if (preg_match('/[^\x20-\x7E]/', $name) || strpbrk($name, '"\\,;<>@') !== false) { return '=?UTF-8?B?' . base64_encode($name) . '?='; } return $name; }