Files
email-api/app.py
cproudlock 5faf8817de Initial commit: Python email notification API
- Flask-based email API for manufacturing notifications
- SMTP integration with configurable settings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 13:15:05 -05:00

338 lines
11 KiB
Python

"""
ShopDB Email API
================
Flask API for sending emails via SMTP with STARTTLS support.
Designed to be called from Classic ASP pages that cannot handle STARTTLS.
Environment Variables:
SMTP_HOST - SMTP server hostname
SMTP_PORT - SMTP port (default: 587)
SMTP_USER - SMTP username
SMTP_PASS - SMTP password
SMTP_FROM - Default from address (optional)
API_KEY - Simple API key for authentication (optional)
DB_HOST - MySQL host for distribution groups (default: 192.168.122.1)
DB_USER - MySQL username (default: 570005354)
DB_PASS - MySQL password
DB_NAME - MySQL database (default: shopdb)
Usage from ASP:
Set http = Server.CreateObject("MSXML2.ServerXMLHTTP.6.0")
http.open "POST", "http://localhost:5000/api/send", False
http.setRequestHeader "Content-Type", "application/json"
http.send "{""to"":""user@example.com"",""subject"":""Test"",""message"":""Hello""}"
"""
import os
import smtplib
import logging
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from flask import Flask, request, jsonify
from functools import wraps
# Optional MySQL support for distribution groups
try:
import mysql.connector
MYSQL_AVAILABLE = True
except ImportError:
MYSQL_AVAILABLE = False
app = Flask(__name__)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Configuration from environment
SMTP_HOST = os.environ.get('SMTP_HOST', '')
SMTP_PORT = int(os.environ.get('SMTP_PORT', '587'))
SMTP_USER = os.environ.get('SMTP_USER', '')
SMTP_PASS = os.environ.get('SMTP_PASS', '')
SMTP_FROM = os.environ.get('SMTP_FROM', SMTP_USER)
API_KEY = os.environ.get('API_KEY', '')
# Database config for distribution groups
DB_HOST = os.environ.get('DB_HOST', '192.168.122.1')
DB_PORT = int(os.environ.get('DB_PORT', '3306'))
DB_USER = os.environ.get('DB_USER', '570005354')
DB_PASS = os.environ.get('DB_PASS', '570005354')
DB_NAME = os.environ.get('DB_NAME', 'shopdb')
def require_api_key(f):
"""Optional API key authentication decorator."""
@wraps(f)
def decorated(*args, **kwargs):
if API_KEY:
provided_key = request.headers.get('X-API-Key') or request.args.get('api_key')
if provided_key != API_KEY:
return jsonify({'success': False, 'error': 'Invalid or missing API key'}), 401
return f(*args, **kwargs)
return decorated
def get_db_connection():
"""Get MySQL database connection."""
if not MYSQL_AVAILABLE:
return None
try:
return mysql.connector.connect(
host=DB_HOST,
port=DB_PORT,
user=DB_USER,
password=DB_PASS,
database=DB_NAME
)
except Exception as e:
logger.error(f"Database connection failed: {e}")
return None
def send_email(to_addresses, subject, message, html=False, from_addr=None):
"""
Send email via SMTP with STARTTLS.
Args:
to_addresses: Single email or list of emails
subject: Email subject
message: Email body (plain text or HTML)
html: If True, send as HTML email
from_addr: Optional from address (defaults to SMTP_FROM)
Returns:
tuple: (success: bool, error_message: str or None)
"""
if not SMTP_HOST or not SMTP_USER or not SMTP_PASS:
return False, "SMTP not configured. Set SMTP_HOST, SMTP_USER, SMTP_PASS environment variables."
# Normalize to list
if isinstance(to_addresses, str):
to_addresses = [to_addresses]
# Filter empty addresses
to_addresses = [addr.strip() for addr in to_addresses if addr and addr.strip()]
if not to_addresses:
return False, "No valid recipient addresses provided"
from_addr = from_addr or SMTP_FROM
try:
# Create message
if html:
msg = MIMEMultipart('alternative')
msg.attach(MIMEText(message, 'html'))
else:
msg = MIMEText(message, 'plain')
msg['Subject'] = subject
msg['From'] = from_addr
msg['To'] = ', '.join(to_addresses)
# Connect and send
logger.info(f"Connecting to {SMTP_HOST}:{SMTP_PORT}")
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as server:
server.ehlo()
server.starttls()
server.ehlo()
server.login(SMTP_USER, SMTP_PASS)
server.sendmail(from_addr, to_addresses, msg.as_string())
logger.info(f"Email sent successfully to {to_addresses}")
return True, None
except smtplib.SMTPAuthenticationError as e:
error = f"SMTP authentication failed: {e}"
logger.error(error)
return False, error
except smtplib.SMTPException as e:
error = f"SMTP error: {e}"
logger.error(error)
return False, error
except Exception as e:
error = f"Failed to send email: {e}"
logger.error(error)
return False, error
@app.route('/api/health', methods=['GET'])
def health_check():
"""Health check endpoint."""
smtp_configured = bool(SMTP_HOST and SMTP_USER and SMTP_PASS)
return jsonify({
'status': 'ok',
'smtp_configured': smtp_configured,
'mysql_available': MYSQL_AVAILABLE
})
@app.route('/api/send', methods=['POST'])
@require_api_key
def send_email_endpoint():
"""
Send an email.
Request JSON:
{
"to": "email@example.com" or ["email1@example.com", "email2@example.com"],
"subject": "Email subject",
"message": "Email body",
"html": false, // optional, default false
"from": "sender@example.com" // optional
}
Response JSON:
{"success": true} or {"success": false, "error": "error message"}
"""
try:
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'No JSON data provided'}), 400
to_addresses = data.get('to')
subject = data.get('subject', '')
message = data.get('message', '')
html = data.get('html', False)
from_addr = data.get('from')
if not to_addresses:
return jsonify({'success': False, 'error': 'Missing "to" field'}), 400
if not subject:
return jsonify({'success': False, 'error': 'Missing "subject" field'}), 400
if not message:
return jsonify({'success': False, 'error': 'Missing "message" field'}), 400
success, error = send_email(to_addresses, subject, message, html, from_addr)
if success:
return jsonify({'success': True})
else:
return jsonify({'success': False, 'error': error}), 500
except Exception as e:
logger.error(f"Error in send endpoint: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/send-to-group', methods=['POST'])
@require_api_key
def send_to_distribution_group():
"""
Send email to a distribution group from the database.
Request JSON:
{
"group_id": 1, // or "group_name": "IT Support"
"subject": "Email subject",
"message": "Email body",
"html": false
}
"""
if not MYSQL_AVAILABLE:
return jsonify({'success': False, 'error': 'MySQL connector not installed'}), 500
try:
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'No JSON data provided'}), 400
group_id = data.get('group_id')
group_name = data.get('group_name')
subject = data.get('subject', '')
message = data.get('message', '')
html = data.get('html', False)
if not group_id and not group_name:
return jsonify({'success': False, 'error': 'Missing group_id or group_name'}), 400
# Look up distribution group
conn = get_db_connection()
if not conn:
return jsonify({'success': False, 'error': 'Database connection failed'}), 500
try:
cursor = conn.cursor(dictionary=True)
if group_id:
cursor.execute(
"SELECT email FROM distributiongroups WHERE distributiongroupid = %s AND isactive = 1",
(group_id,)
)
else:
cursor.execute(
"SELECT email FROM distributiongroups WHERE name = %s AND isactive = 1",
(group_name,)
)
result = cursor.fetchone()
if not result:
return jsonify({'success': False, 'error': 'Distribution group not found or inactive'}), 404
to_address = result['email']
finally:
cursor.close()
conn.close()
success, error = send_email(to_address, subject, message, html)
if success:
return jsonify({'success': True, 'sent_to': to_address})
else:
return jsonify({'success': False, 'error': error}), 500
except Exception as e:
logger.error(f"Error in send-to-group endpoint: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/groups', methods=['GET'])
@require_api_key
def list_distribution_groups():
"""List all active distribution groups."""
if not MYSQL_AVAILABLE:
return jsonify({'success': False, 'error': 'MySQL connector not installed'}), 500
conn = get_db_connection()
if not conn:
return jsonify({'success': False, 'error': 'Database connection failed'}), 500
try:
cursor = conn.cursor(dictionary=True)
cursor.execute(
"SELECT distributiongroupid, name, email FROM distributiongroups WHERE isactive = 1 ORDER BY name"
)
groups = cursor.fetchall()
return jsonify({'success': True, 'groups': groups})
except Exception as e:
logger.error(f"Error listing groups: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
finally:
cursor.close()
conn.close()
if __name__ == '__main__':
# Check configuration
if not SMTP_HOST:
logger.warning("SMTP_HOST not set - email sending will fail")
if not SMTP_USER:
logger.warning("SMTP_USER not set - email sending will fail")
if not SMTP_PASS:
logger.warning("SMTP_PASS not set - email sending will fail")
logger.info(f"Starting Email API on port 5000")
logger.info(f"SMTP: {SMTP_HOST}:{SMTP_PORT}")
app.run(host='0.0.0.0', port=5000, debug=False)