Fixed startup email

This commit is contained in:
Jonas Linter
2025-10-16 10:53:03 +02:00
parent 90e253b950
commit 6ad4df6990
4 changed files with 336 additions and 31 deletions

View File

@@ -14,11 +14,9 @@ server:
companyname: "99tales Gmbh" companyname: "99tales Gmbh"
res_id_source_context: "99tales" res_id_source_context: "99tales"
logger: logger:
level: "INFO" # Set to DEBUG for more verbose output level: "INFO" # Set to DEBUG for more verbose output
file: "alpinebits.log" # Log file path, or null for console only file: "alpinebits.log" # Log file path, or null for console only
alpine_bits_auth: alpine_bits_auth:
- hotel_id: "39054_001" - hotel_id: "39054_001"
@@ -40,48 +38,48 @@ alpine_bits_auth:
username: "erika" username: "erika"
password: !secret ERIKA_PASSWORD password: !secret ERIKA_PASSWORD
api_tokens:
- tLTI8wXF1OVEvUX7kdZRhSW3Qr5feBCz0mHo-kbnEp0
# Email configuration for monitoring and alerts # Email configuration for monitoring and alerts
email: email:
# SMTP server configuration # SMTP server configuration
smtp: smtp:
host: "smtp.titan.email" # Your SMTP server host: "smtp.titan.email" # Your SMTP server
port: 465 # Usually 587 for TLS, 465 for SSL port: 465 # Usually 587 for TLS, 465 for SSL
username: info@99tales.net # SMTP username username: info@99tales.net # SMTP username
password: !secret EMAIL_PASSWORD # SMTP password password: !secret EMAIL_PASSWORD # SMTP password
use_tls: false # Use STARTTLS use_tls: false # Use STARTTLS
use_ssl: true # Use SSL/TLS from start use_ssl: true # Use SSL/TLS from start
# Email addresses # Email addresses
from_address: "info@99tales.net" # Sender address from_address: "info@99tales.net" # Sender address
from_name: "AlpineBits Monitor" # Sender display name from_name: "AlpineBits Monitor" # Sender display name
api_tokens:
- tLTI8wXF1OVEvUX7kdZRhSW3Qr5feBCz0mHo-kbnEp0
# Monitoring and alerting # Monitoring and alerting
monitoring: monitoring:
# Daily report configuration # Daily report configuration
daily_report: daily_report:
enabled: false # Set to true to enable daily reports enabled: false # Set to true to enable daily reports
recipients: recipients:
- "jonas@vaius.ai" - "jonas@vaius.ai"
#- "dev@99tales.com" #- "dev@99tales.com"
send_time: "08:00" # Time to send daily report (24h format, local time) send_time: "08:00" # Time to send daily report (24h format, local time)
include_stats: true # Include reservation/customer stats include_stats: true # Include reservation/customer stats
include_errors: true # Include error summary include_errors: true # Include error summary
# Error alert configuration (hybrid approach) # Error alert configuration (hybrid approach)
error_alerts: error_alerts:
enabled: false # Set to true to enable error alerts enabled: false # Set to true to enable error alerts
recipients: recipients:
- "jonas@vaius.ai" - "jonas@vaius.ai"
#- "oncall@99tales.com" #- "oncall@99tales.com"
# Alert is sent immediately if threshold is reached # Alert is sent immediately if threshold is reached
error_threshold: 5 # Send immediate alert after N errors error_threshold: 5 # Send immediate alert after N errors
# Otherwise, alert is sent after buffer time expires # Otherwise, alert is sent after buffer time expires
buffer_minutes: 15 # Wait N minutes before sending buffered errors buffer_minutes: 15 # Wait N minutes before sending buffered errors
# Cooldown period to prevent alert spam # Cooldown period to prevent alert spam
cooldown_minutes: 15 # Wait N min before sending another alert cooldown_minutes: 15 # Wait N min before sending another alert
# Error severity levels to monitor # Error severity levels to monitor
log_levels: log_levels:
- "ERROR" - "ERROR"

View File

@@ -299,10 +299,11 @@ async def lifespan(app: FastAPI):
report_scheduler.set_stats_collector(stats_collector.collect_stats) report_scheduler.set_stats_collector(stats_collector.collect_stats)
_LOGGER.info("Stats collector initialized and hooked up to report scheduler") _LOGGER.info("Stats collector initialized and hooked up to report scheduler")
# Send a test daily report on startup for testing # Send a test daily report on startup for testing (with 24-hour lookback)
_LOGGER.info("Sending test daily report on startup") _LOGGER.info("Sending test daily report on startup (last 24 hours)")
try: try:
stats = await stats_collector.collect_stats() # Use lookback_hours=24 to get stats from last 24 hours
stats = await stats_collector.collect_stats(lookback_hours=24)
success = await email_service.send_daily_report( success = await email_service.send_daily_report(
recipients=report_scheduler.recipients, recipients=report_scheduler.recipients,
stats=stats, stats=stats,

View File

@@ -492,16 +492,27 @@ class ReservationStatsCollector:
len(self._hotel_map), len(self._hotel_map),
) )
async def collect_stats(self) -> dict[str, Any]: async def collect_stats(self, lookback_hours: int | None = None) -> dict[str, Any]:
"""Collect reservation statistics for the reporting period. """Collect reservation statistics for the reporting period.
Args:
lookback_hours: Optional override to look back N hours from now.
If None, uses time since last report.
Returns: Returns:
Dictionary with statistics including reservations per hotel Dictionary with statistics including reservations per hotel
""" """
now = datetime.now() now = datetime.now()
period_start = self._last_report_time
period_end = now if lookback_hours is not None:
# Override mode: look back N hours from now
period_start = now - timedelta(hours=lookback_hours)
period_end = now
else:
# Normal mode: since last report
period_start = self._last_report_time
period_end = now
_LOGGER.info( _LOGGER.info(
"Collecting reservation stats from %s to %s", "Collecting reservation stats from %s to %s",
@@ -538,8 +549,9 @@ class ReservationStatsCollector:
# Sort by reservation count descending # Sort by reservation count descending
hotels_stats.sort(key=lambda x: x["reservations"], reverse=True) hotels_stats.sort(key=lambda x: x["reservations"], reverse=True)
# Update last report time # Update last report time only in normal mode (not lookback mode)
self._last_report_time = now if lookback_hours is None:
self._last_report_time = now
stats = { stats = {
"reporting_period": { "reporting_period": {

294
test_smtp.py Normal file
View File

@@ -0,0 +1,294 @@
#!/usr/bin/env python3
"""Test script to diagnose SMTP connection issues.
This script tests SMTP connectivity with different configurations to help
identify whether the issue is with credentials, network, ports, or TLS settings.
"""
import smtplib
import ssl
import sys
from datetime import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# Load configuration from config.yaml
try:
from alpine_bits_python.config_loader import load_config
print("Loading configuration from config.yaml...")
config = load_config()
email_config = config.get("email", {})
smtp_config = email_config.get("smtp", {})
SMTP_HOST = smtp_config.get("host", "smtp.titan.email")
SMTP_PORT = smtp_config.get("port", 465)
SMTP_USERNAME = smtp_config.get("username", "")
SMTP_PASSWORD = smtp_config.get("password", "")
USE_TLS = smtp_config.get("use_tls", False)
USE_SSL = smtp_config.get("use_ssl", True)
FROM_ADDRESS = email_config.get("from_address", "info@99tales.net")
FROM_NAME = email_config.get("from_name", "AlpineBits Monitor")
# Get test recipient
monitoring_config = email_config.get("monitoring", {})
daily_report = monitoring_config.get("daily_report", {})
recipients = daily_report.get("recipients", [])
TEST_RECIPIENT = recipients[0] if recipients else "jonas@vaius.ai"
print(f"✓ Configuration loaded successfully")
print(f" SMTP Host: {SMTP_HOST}")
print(f" SMTP Port: {SMTP_PORT}")
print(f" Username: {SMTP_USERNAME}")
print(f" Password: {'***' if SMTP_PASSWORD else '(not set)'}")
print(f" Use SSL: {USE_SSL}")
print(f" Use TLS: {USE_TLS}")
print(f" From: {FROM_ADDRESS}")
print(f" Test Recipient: {TEST_RECIPIENT}")
print()
except Exception as e:
print(f"✗ Failed to load configuration: {e}")
print("Using default values for testing...")
SMTP_HOST = "smtp.titan.email"
SMTP_PORT = 465
SMTP_USERNAME = input("Enter SMTP username: ")
SMTP_PASSWORD = input("Enter SMTP password: ")
USE_TLS = False
USE_SSL = True
FROM_ADDRESS = "info@99tales.net"
FROM_NAME = "AlpineBits Monitor"
TEST_RECIPIENT = input("Enter test recipient email: ")
print()
def create_test_message(subject: str) -> MIMEMultipart:
"""Create a test email message."""
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = f"{FROM_NAME} <{FROM_ADDRESS}>"
msg["To"] = TEST_RECIPIENT
msg["Date"] = datetime.now().strftime("%a, %d %b %Y %H:%M:%S %z")
body = f"""SMTP Connection Test - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
This is a test email to verify SMTP connectivity.
Configuration:
- SMTP Host: {SMTP_HOST}
- SMTP Port: {SMTP_PORT}
- Use SSL: {USE_SSL}
- Use TLS: {USE_TLS}
If you received this email, the SMTP configuration is working correctly!
"""
msg.attach(MIMEText(body, "plain"))
return msg
def test_smtp_connection(host: str, port: int, timeout: int = 10) -> bool:
"""Test basic TCP connection to SMTP server."""
import socket
print(f"Test 1: Testing TCP connection to {host}:{port}...")
try:
sock = socket.create_connection((host, port), timeout=timeout)
sock.close()
print(f"✓ TCP connection successful to {host}:{port}")
return True
except socket.timeout:
print(f"✗ Connection timed out after {timeout} seconds")
print(f" This suggests a network/firewall issue blocking access to {host}:{port}")
return False
except socket.error as e:
print(f"✗ Connection failed: {e}")
return False
def test_smtp_ssl(host: str, port: int, username: str, password: str, timeout: int = 30) -> bool:
"""Test SMTP connection with SSL."""
print(f"\nTest 2: Testing SMTP with SSL (port {port})...")
try:
context = ssl.create_default_context()
with smtplib.SMTP_SSL(host, port, timeout=timeout, context=context) as server:
print(f"✓ Connected to SMTP server with SSL")
# Try to get server info
server.ehlo()
print(f"✓ EHLO successful")
# Try authentication if credentials provided
if username and password:
print(f" Attempting authentication as: {username}")
server.login(username, password)
print(f"✓ Authentication successful")
else:
print(f"⚠ No credentials provided, skipping authentication")
return True
except smtplib.SMTPAuthenticationError as e:
print(f"✗ Authentication failed: {e}")
print(f" Check your username and password")
return False
except socket.timeout:
print(f"✗ Connection timed out after {timeout} seconds")
print(f" Try increasing timeout or check network/firewall")
return False
except Exception as e:
print(f"✗ SMTP SSL failed: {e}")
return False
def test_smtp_tls(host: str, port: int, username: str, password: str, timeout: int = 30) -> bool:
"""Test SMTP connection with STARTTLS."""
print(f"\nTest 3: Testing SMTP with STARTTLS (port {port})...")
try:
with smtplib.SMTP(host, port, timeout=timeout) as server:
print(f"✓ Connected to SMTP server")
# Try STARTTLS
context = ssl.create_default_context()
server.starttls(context=context)
print(f"✓ STARTTLS successful")
# Try authentication if credentials provided
if username and password:
print(f" Attempting authentication as: {username}")
server.login(username, password)
print(f"✓ Authentication successful")
else:
print(f"⚠ No credentials provided, skipping authentication")
return True
except smtplib.SMTPAuthenticationError as e:
print(f"✗ Authentication failed: {e}")
return False
except socket.timeout:
print(f"✗ Connection timed out after {timeout} seconds")
return False
except Exception as e:
print(f"✗ SMTP TLS failed: {e}")
return False
def send_test_email(host: str, port: int, username: str, password: str,
use_ssl: bool, use_tls: bool, timeout: int = 30) -> bool:
"""Send an actual test email."""
print(f"\nTest 4: Sending test email...")
try:
msg = create_test_message("SMTP Test Email - AlpineBits")
if use_ssl:
context = ssl.create_default_context()
with smtplib.SMTP_SSL(host, port, timeout=timeout, context=context) as server:
if username and password:
server.login(username, password)
server.send_message(msg, FROM_ADDRESS, [TEST_RECIPIENT])
else:
with smtplib.SMTP(host, port, timeout=timeout) as server:
if use_tls:
context = ssl.create_default_context()
server.starttls(context=context)
if username and password:
server.login(username, password)
server.send_message(msg, FROM_ADDRESS, [TEST_RECIPIENT])
print(f"✓ Test email sent successfully to {TEST_RECIPIENT}")
print(f" Check your inbox!")
return True
except Exception as e:
print(f"✗ Failed to send email: {e}")
return False
def main():
"""Run all SMTP tests."""
print("=" * 70)
print("SMTP Connection Test Script")
print("=" * 70)
print()
# Test 1: Basic TCP connection
tcp_ok = test_smtp_connection(SMTP_HOST, SMTP_PORT, timeout=10)
if not tcp_ok:
print("\n" + "=" * 70)
print("DIAGNOSIS: Cannot establish TCP connection to SMTP server")
print("=" * 70)
print("\nPossible causes:")
print("1. The SMTP server is down or unreachable")
print("2. A firewall is blocking the connection")
print("3. The host or port is incorrect")
print("4. Network connectivity issues from your container/server")
print("\nTroubleshooting:")
print(f"- Verify the server is correct: {SMTP_HOST}")
print(f"- Verify the port is correct: {SMTP_PORT}")
print("- Check if your container/server has outbound internet access")
print("- Try from a different network or machine")
print(f"- Use telnet/nc to test: telnet {SMTP_HOST} {SMTP_PORT}")
return 1
# Test 2 & 3: Try both SSL and TLS
ssl_ok = False
tls_ok = False
if USE_SSL:
ssl_ok = test_smtp_ssl(SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, timeout=30)
# Also try common alternative ports
if not ssl_ok and SMTP_PORT == 465:
print("\n⚠ Port 465 failed, trying port 587 with STARTTLS...")
tls_ok = test_smtp_tls(SMTP_HOST, 587, SMTP_USERNAME, SMTP_PASSWORD, timeout=30)
if USE_TLS:
tls_ok = test_smtp_tls(SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, timeout=30)
if not ssl_ok and not tls_ok:
print("\n" + "=" * 70)
print("DIAGNOSIS: Cannot authenticate or establish secure connection")
print("=" * 70)
print("\nPossible causes:")
print("1. Wrong username or password")
print("2. Wrong port for the encryption method")
print("3. SSL/TLS version mismatch")
print("\nTroubleshooting:")
print("- Verify your credentials are correct")
print("- Port 465 typically uses SSL")
print("- Port 587 typically uses STARTTLS")
print("- Port 25 is usually unencrypted (not recommended)")
return 1
# Test 4: Send actual email
send_ok = send_test_email(
SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD,
USE_SSL, USE_TLS, timeout=30
)
print("\n" + "=" * 70)
if send_ok:
print("✓ ALL TESTS PASSED!")
print("=" * 70)
print("\nYour SMTP configuration is working correctly.")
print(f"Check {TEST_RECIPIENT} for the test email.")
else:
print("⚠ PARTIAL SUCCESS")
print("=" * 70)
print("\nConnection and authentication work, but email sending failed.")
print("This might be a temporary issue. Try again.")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
print("\n\nTest cancelled by user")
sys.exit(1)