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>
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
337
app.py
Normal file
337
app.py
Normal file
@@ -0,0 +1,337 @@
|
||||
"""
|
||||
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)
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
flask>=2.0.0
|
||||
mysql-connector-python>=8.0.0
|
||||
31
start.sh
Executable file
31
start.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Start the Email API service
|
||||
#
|
||||
# Required environment variables:
|
||||
# SMTP_HOST - SMTP server hostname
|
||||
# SMTP_USER - SMTP username
|
||||
# SMTP_PASS - SMTP password
|
||||
#
|
||||
# Optional:
|
||||
# SMTP_PORT - SMTP port (default: 587)
|
||||
# SMTP_FROM - Default from address
|
||||
# API_KEY - API key for authentication
|
||||
# DB_HOST - MySQL host (default: 192.168.122.1)
|
||||
# DB_USER - MySQL user (default: 570005354)
|
||||
# DB_PASS - MySQL password
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Check for virtual environment
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
else
|
||||
source venv/bin/activate
|
||||
fi
|
||||
|
||||
# Start the API
|
||||
echo "Starting Email API on port 5000..."
|
||||
python app.py
|
||||
Reference in New Issue
Block a user