', 250); sendCommand($socket, 'RCPT TO:<' . bareMailAddress($to) . '>', 250); sendCommand($socket, 'DATA', 354); $message = buildMessage($fromHeader, $to, $subject, $body); fwrite($socket, $message); expectCode($socket, 250); sendCommand($socket, 'QUIT', 221); } finally { fclose($socket); } } /** @return resource */ function connectSmtp(string $host, int $port, string $encryption) { $target = $encryption === 'ssl' ? "ssl://{$host}:{$port}" : "tcp://{$host}:{$port}"; $socket = @stream_socket_client( $target, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, stream_context_create(['ssl' => ['verify_peer' => true, 'verify_peer_name' => true]]) ); if ($socket === false) { throw new RuntimeException("SMTP connect failed: {$errstr} ({$errno})"); } stream_set_timeout($socket, 30); return $socket; } /** @param resource $socket */ function sendCommand($socket, string $command, int $expectedCode): void { fwrite($socket, $command . "\r\n"); expectCode($socket, $expectedCode); } /** @param resource $socket */ function expectCode($socket, int $expectedCode): void { $response = readResponse($socket); $code = (int) substr($response, 0, 3); if ($code !== $expectedCode) { throw new RuntimeException("SMTP error (expected {$expectedCode}): {$response}"); } } /** @param resource $socket */ function readResponse($socket): string { $response = ''; while (($line = fgets($socket, 515)) !== false) { $response .= $line; if (isset($line[3]) && $line[3] === ' ') { break; } } return trim($response); } function bareMailAddress(string $address): string { $address = trim($address); if (preg_match('/<([^>]+)>/', $address, $matches)) { return trim($matches[1]); } return $address; } function encodeDisplayName(string $name): string { if (preg_match('/[\r\n"]/', $name)) { throw new InvalidArgumentException('Invalid display name'); } if (preg_match('/^[A-Za-z0-9 !\'.\-]+$/', $name)) { return $name; } return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $name) . '"'; } function formatMailAddress(string $email, string $displayName): string { $email = bareMailAddress($email); return encodeDisplayName($displayName) . ' <' . $email . '>'; } function buildMessage(string $from, string $to, string $subject, string $body): string { $domain = env('DOMAIN', '0012.de'); $date = gmdate('D, d M Y H:i:s') . ' +0000'; // MTA-style ID (time + pid) — avoids SpamAssassin MSGID_RANDY on random hex IDs. $messageId = sprintf('<%s.%s@%s>', gmdate('YmdHis'), getmypid(), $domain); $fromHeader = formatMailAddress($from, env('SMTP_FROM_NAME', '0012 Monitoring')); $toHeader = formatMailAddress($to, env('MAIL_TO_NAME', 'Alert Recipient')); $headers = [ "From: {$fromHeader}", "To: {$toHeader}", "Subject: {$subject}", "Date: {$date}", "Message-ID: {$messageId}", 'MIME-Version: 1.0', 'Content-Type: text/plain; charset=UTF-8', 'Content-Transfer-Encoding: 8bit', ]; return implode("\r\n", $headers) . "\r\n\r\n" . $body . "\r\n.\r\n"; } function sendAlert(string $subject, string $body): void { $to = env('ALERT_EMAIL'); if (!$to) { throw new RuntimeException('ALERT_EMAIL not set in .env'); } sendMail($to, $subject, $body); }