nonzeroexit commited on
Commit
3959ff2
·
verified ·
1 Parent(s): 45a55f0

Update email_sender_api.py

Browse files
Files changed (1) hide show
  1. email_sender_api.py +69 -105
email_sender_api.py CHANGED
@@ -1,33 +1,15 @@
1
- from flask import Flask, request, jsonify
2
- from flask_cors import CORS
3
- import smtplib
4
- from email.mime.multipart import MIMEMultipart
5
- from email.mime.text import MIMEText
6
- from email.mime.base import MIMEBase
7
- from email import encoders
8
- import base64
9
- import os
10
- import socket # For socket-related exceptions and timeout
11
- import traceback # For detailed error printing
12
 
13
- app = Flask(__name__)
14
- CORS(app)
15
 
16
- # Environment variables will be set as Secrets in Hugging Face Space settings
17
- SMTP_SERVER_HOST = os.environ.get("SMTP_SERVER_HOST")
18
- SMTP_SERVER_PORT = os.environ.get("SMTP_SERVER_PORT")
19
- SMTP_SENDER_EMAIL = os.environ.get("SMTP_SENDER_EMAIL")
20
- SMTP_SENDER_PASSWORD = os.environ.get("SMTP_SENDER_PASSWORD")
21
-
22
- SMTP_CONNECTION_TIMEOUT = 20 # seconds for connection
23
- SMTP_COMMAND_TIMEOUT = 20 # seconds for individual commands
24
-
25
- @app.route('/send-report-via-email', methods=['POST'])
26
  def handle_send_email():
27
- request_id = base64.b64encode(os.urandom(6)).decode('utf-8') # Simple request ID for logging
28
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Received request at /send-report-via-email")
29
 
30
  if not all([SMTP_SERVER_HOST, SMTP_SERVER_PORT, SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD]):
 
31
  error_msg = "EMAIL_SENDER_API (HF) [{request_id}]: ERROR - SMTP environment variables (Secrets) not fully set."
32
  print(error_msg)
33
  return jsonify({"status": "error", "message": "Email server configuration incomplete on the API."}), 500
@@ -35,130 +17,112 @@ def handle_send_email():
35
  try:
36
  SMTP_PORT_INT = int(SMTP_SERVER_PORT)
37
  except ValueError:
 
38
  error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: ERROR - Invalid SMTP_SERVER_PORT: '{SMTP_SERVER_PORT}'."
39
  print(error_msg)
40
  return jsonify({"status": "error", "message": "Invalid email server port configured on the API."}), 500
41
 
42
  data = request.json
43
  if not data:
44
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: No JSON data received.")
 
45
  return jsonify({"status": "error", "message": "No data received by email API."}), 400
46
 
47
  recipient_email = data.get('recipient_email')
48
- pdf_base64_data = data.get('pdf_base64_data')
49
- pdf_filename = data.get('pdf_filename')
50
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Attempting to send to: {recipient_email}, Filename: {pdf_filename}")
51
 
52
- if not all([recipient_email, pdf_base64_data, pdf_filename]):
53
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Missing required data in JSON payload.")
54
- return jsonify({"status": "error", "message": "Missing required data: recipient_email, pdf_base64_data, or pdf_filename."}), 400
55
 
56
- try:
57
- msg = MIMEMultipart()
58
- msg['From'] = SMTP_SENDER_EMAIL
59
- msg['To'] = recipient_email
60
- msg['Subject'] = f"EPIC-AMP Analysis Report: {pdf_filename}" # Subject kept as is
61
- body = f"""Dear User,\n\nPlease find your EPIC-AMP analysis report attached.\n\nFilename: {pdf_filename}\n\nThis report includes details on your sequence analysis.\n\nThank you for using EPIC-AMP!\n\nBest regards,\nThe EPIC-AMP Team\nBioinformatics and Computational Biology Unit, Zewail City\nFor inquiries: [email protected]"""
62
- msg.attach(MIMEText(body, 'plain'))
63
 
64
- pdf_data_bytes = base64.b64decode(pdf_base64_data)
65
- part = MIMEBase('application', 'octet-stream')
66
- part.set_payload(pdf_data_bytes)
67
- encoders.encode_base64(part)
68
- part.add_header('Content-Disposition', f"attachment; filename=\"{pdf_filename}\"")
69
- msg.attach(part)
 
 
 
70
 
71
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Preparing to connect to SMTP server {SMTP_SERVER_HOST}:{SMTP_PORT_INT} with timeout {SMTP_CONNECTION_TIMEOUT}s")
72
 
73
- # Using timeout for the SMTP connection itself
74
- # For commands, smtplib uses the same timeout by default or it can be passed to sendmail
75
- server = None # Initialize server to None for finally block
76
  try:
77
  if SMTP_PORT_INT == 465: # SSL
78
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Using SMTP_SSL for port 465.")
79
  server = smtplib.SMTP_SSL(SMTP_SERVER_HOST, SMTP_PORT_INT, timeout=SMTP_CONNECTION_TIMEOUT)
80
  else: # Standard port, try STARTTLS (e.g., for 587)
81
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Using standard SMTP for port {SMTP_PORT_INT}.")
82
  server = smtplib.SMTP(SMTP_SERVER_HOST, SMTP_PORT_INT, timeout=SMTP_CONNECTION_TIMEOUT)
83
 
84
  server.set_debuglevel(1)
85
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Connected. Sending EHLO/HELO...")
86
- server.ehlo() # Or server.helo() if ehlo fails
87
 
88
- if SMTP_PORT_INT == 587: # Attempt STARTTLS if on port 587
89
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Port is 587, attempting STARTTLS...")
90
  server.starttls()
91
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: STARTTLS successful. Re-sending EHLO...")
92
  server.ehlo()
93
 
94
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Attempting login with user: {SMTP_SENDER_EMAIL}...")
95
  server.login(SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD)
96
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Login successful. Sending mail to {recipient_email}...")
97
- # The sendmail command will also use the timeout set on the SMTP object.
98
- server.sendmail(SMTP_SENDER_EMAIL, recipient_email, msg.as_string())
99
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Mail sent successfully.")
100
 
101
  finally:
102
  if server:
103
  try:
104
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Quitting SMTP server connection.")
105
  server.quit()
106
  except Exception as e_quit:
107
- print(f"EMAIL_SENDER_API (HF) [{request_id}]: Error during server.quit(): {e_quit}")
108
 
109
- success_msg = f"Report successfully sent to {recipient_email}."
110
  print(f"EMAIL_SENDER_API (HF) [{request_id}]: {success_msg}")
111
  return jsonify({"status": "success", "message": success_msg}), 200
112
 
 
113
  except smtplib.SMTPConnectError as e:
114
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SMTP Connect Error - Could not connect to {SMTP_SERVER_HOST}:{SMTP_PORT_INT}. {e}"
115
  print(error_msg); traceback.print_exc()
116
- return jsonify({"status": "error", "message": f"Could not connect to email server: {e.strerror if hasattr(e, 'strerror') else e}"}), 503 # Service Unavailable
117
- except smtplib.SMTPHeloError as e:
118
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SMTP Helo/Ehlo Error - Server did not reply properly. {e}"
119
  print(error_msg); traceback.print_exc()
120
- return jsonify({"status": "error", "message": "Email server did not respond correctly (HELO/EHLO)."}), 502 # Bad Gateway
121
  except smtplib.SMTPAuthenticationError as e:
122
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SMTP Authentication Error - Check username/password (App Password). {e}"
123
- print(error_msg); traceback.print_exc()
124
- return jsonify({"status": "error", "message": "Email server authentication failed on the API."}), 500
125
- except smtplib.SMTPSenderRefused as e:
126
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SMTP Sender Refused - Server didn't accept from address '{SMTP_SENDER_EMAIL}'. {e}"
127
- print(error_msg); traceback.print_exc()
128
- return jsonify({"status": "error", "message": "Sender email address refused by server."}), 500
129
- except smtplib.SMTPRecipientsRefused as e:
130
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SMTP Recipients Refused - Server didn't accept to address(es). {e.recipients}" # e.recipients is a dict
131
- print(error_msg); traceback.print_exc()
132
- return jsonify({"status": "error", "message": f"Recipient email address(es) refused by server."}), 400 # Bad request if recipient is bad
133
- except smtplib.SMTPDataError as e:
134
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SMTP Data Error - Server replied with an unexpected error code to the DATA command. {e}"
135
- print(error_msg); traceback.print_exc()
136
- return jsonify({"status": "error", "message": "Error sending email content/data."}), 500
137
- except smtplib.SMTPServerDisconnected as e:
138
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SMTP Server Disconnected - {e}"
139
  print(error_msg); traceback.print_exc()
140
- return jsonify({"status": "error", "message": "Email server disconnected unexpectedly."}), 503
 
141
  except socket.timeout as e:
142
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: Socket Timeout during SMTP operation. {e}"
143
  print(error_msg); traceback.print_exc()
144
- return jsonify({"status": "error", "message": "Connection to email server timed out."}), 504 # Gateway Timeout
145
- except socket.gaierror as e: # getaddrinfo error (DNS lookup failure)
146
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: Address-related error connecting to SMTP host '{SMTP_SERVER_HOST}'. Check hostname. {e}"
147
  print(error_msg); traceback.print_exc()
148
- return jsonify({"status": "error", "message": "Could not resolve email server hostname."}), 503
149
- except Exception as e: # Catch-all for other unexpected errors
150
- error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: An unexpected error occurred while sending email to {recipient_email} - {e}"
 
 
 
 
 
 
 
 
 
151
  print(error_msg)
152
- traceback.print_exc() # Print full traceback for unexpected errors
153
- return jsonify({"status": "error", "message": f"An unexpected error occurred on the email API: {type(e).__name__}"}), 500
154
 
155
- # This block is mostly for local testing.
156
- # On Hugging Face Spaces with Gunicorn, Gunicorn directly imports and runs the 'app' object.
157
  # if __name__ == '__main__':
158
- # # For local testing, you would set your ENV VARS in this terminal session before running
159
- # api_port = int(os.environ.get("PORT", 5002)) # Example local port, HF uses PORT from Dockerfile ENV
160
- # print(f"Starting Email Sender API Service locally on port {api_port}...")
161
- # print(f"Local SMTP Config: Server={SMTP_SERVER_HOST}, Port={SMTP_SERVER_PORT}, User Email Set={SMTP_SENDER_EMAIL is not None}, Password Set={SMTP_SENDER_PASSWORD is not None}")
162
- # if not all([SMTP_SERVER_HOST, SMTP_SERVER_PORT, SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD]):
163
- # print("WARNING: LOCAL - SMTP environment variables are not fully set. Email sending will likely fail if requests are made.")
164
- # app.run(host='0.0.0.0', port=api_port, debug=True)
 
1
+ # ... (keep existing imports and Flask app setup, SMTP config variables) ...
 
 
 
 
 
 
 
 
 
 
2
 
3
+ SMTP_CONNECTION_TIMEOUT = 30 # Increased slightly for good measure
4
+ SMTP_COMMAND_TIMEOUT = 30 # Increased slightly for good measure
5
 
6
+ @app.route('/send-report-via-email', methods=['POST']) # Keep the same endpoint
 
 
 
 
 
 
 
 
 
7
  def handle_send_email():
8
+ request_id = base64.b64encode(os.urandom(6)).decode('utf-8')
9
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Received request at /send-report-via-email")
10
 
11
  if not all([SMTP_SERVER_HOST, SMTP_SERVER_PORT, SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD]):
12
+ # ... (same error handling for missing SMTP config as before) ...
13
  error_msg = "EMAIL_SENDER_API (HF) [{request_id}]: ERROR - SMTP environment variables (Secrets) not fully set."
14
  print(error_msg)
15
  return jsonify({"status": "error", "message": "Email server configuration incomplete on the API."}), 500
 
17
  try:
18
  SMTP_PORT_INT = int(SMTP_SERVER_PORT)
19
  except ValueError:
20
+ # ... (same error handling for invalid port) ...
21
  error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: ERROR - Invalid SMTP_SERVER_PORT: '{SMTP_SERVER_PORT}'."
22
  print(error_msg)
23
  return jsonify({"status": "error", "message": "Invalid email server port configured on the API."}), 500
24
 
25
  data = request.json
26
  if not data:
27
+ # ... (same error handling for no data) ...
28
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - No JSON data received.")
29
  return jsonify({"status": "error", "message": "No data received by email API."}), 400
30
 
31
  recipient_email = data.get('recipient_email')
32
+ # We are not using pdf_base64_data or pdf_filename in this simplified test
 
 
33
 
34
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Attempting to send to: {recipient_email}")
 
 
35
 
36
+ if not recipient_email: # Only recipient email is strictly needed for this test
37
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Missing recipient_email in JSON payload.")
38
+ return jsonify({"status": "error", "message": "Missing required data: recipient_email."}), 400
 
 
 
 
39
 
40
+ try:
41
+ # Create a simple plain text message
42
+ simple_msg = MIMEMultipart() # Still use MIMEMultipart for proper headers
43
+ simple_msg['From'] = SMTP_SENDER_EMAIL
44
+ simple_msg['To'] = recipient_email
45
+ simple_msg['Subject'] = "EPIC-AMP - Simple Connectivity Test"
46
+
47
+ simple_body = f"Hello {recipient_email},\n\nThis is a simple plain text email to test SMTP connectivity from the EPIC-AMP Hugging Face Space.\n\nIf you receive this, basic SMTP connection, authentication, and sending are working.\n\nRegards,\nThe EPIC-AMP Test System"
48
+ simple_msg.attach(MIMEText(simple_body, 'plain'))
49
 
50
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Preparing to connect to SMTP server {SMTP_SERVER_HOST}:{SMTP_PORT_INT} with timeout {SMTP_CONNECTION_TIMEOUT}s")
51
 
52
+ server = None
 
 
53
  try:
54
  if SMTP_PORT_INT == 465: # SSL
55
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Using SMTP_SSL for port 465.")
56
  server = smtplib.SMTP_SSL(SMTP_SERVER_HOST, SMTP_PORT_INT, timeout=SMTP_CONNECTION_TIMEOUT)
57
  else: # Standard port, try STARTTLS (e.g., for 587)
58
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Using standard SMTP for port {SMTP_PORT_INT}.")
59
  server = smtplib.SMTP(SMTP_SERVER_HOST, SMTP_PORT_INT, timeout=SMTP_CONNECTION_TIMEOUT)
60
 
61
  server.set_debuglevel(1)
62
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Connected. Sending EHLO/HELO...")
63
+ server.ehlo()
64
 
65
+ if SMTP_PORT_INT == 587:
66
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Port is 587, attempting STARTTLS...")
67
  server.starttls()
68
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - STARTTLS successful. Re-sending EHLO...")
69
  server.ehlo()
70
 
71
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Attempting login with user: {SMTP_SENDER_EMAIL}...")
72
  server.login(SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD)
73
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Login successful. Sending mail to {recipient_email}...")
74
+ server.sendmail(SMTP_SENDER_EMAIL, recipient_email, simple_msg.as_string())
75
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Mail sent successfully.")
 
76
 
77
  finally:
78
  if server:
79
  try:
80
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Quitting SMTP server connection.")
81
  server.quit()
82
  except Exception as e_quit:
83
+ print(f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Error during server.quit(): {e_quit}")
84
 
85
+ success_msg = f"SIMPLE TEST: Email successfully sent to {recipient_email}."
86
  print(f"EMAIL_SENDER_API (HF) [{request_id}]: {success_msg}")
87
  return jsonify({"status": "success", "message": success_msg}), 200
88
 
89
+ # Keep all the specific exception handling blocks as they were
90
  except smtplib.SMTPConnectError as e:
91
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - SMTP Connect Error. {e}"
92
  print(error_msg); traceback.print_exc()
93
+ return jsonify({"status": "error", "message": f"SIMPLIFIED TEST - Could not connect: {e.strerror if hasattr(e, 'strerror') else e}"}), 503
94
+ except smtplib.SMTPHeloError as e: # Keep other specific exceptions
95
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - SMTP Helo/Ehlo Error. {e}"
96
  print(error_msg); traceback.print_exc()
97
+ return jsonify({"status": "error", "message": "SIMPLIFIED TEST - Server HELO/EHLO error."}), 502
98
  except smtplib.SMTPAuthenticationError as e:
99
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - SMTP Authentication Error. {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  print(error_msg); traceback.print_exc()
101
+ return jsonify({"status": "error", "message": "SIMPLIFIED TEST - Email server authentication failed."}), 500
102
+ # ... include ALL other smtplib exceptions and socket exceptions from the previous version of email_sender_api.py ...
103
  except socket.timeout as e:
104
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Socket Timeout. {e}"
105
  print(error_msg); traceback.print_exc()
106
+ return jsonify({"status": "error", "message": "SIMPLIFIED TEST - Connection timed out."}), 504
107
+ except socket.gaierror as e:
108
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - Address-related error. {e}"
109
  print(error_msg); traceback.print_exc()
110
+ return jsonify({"status": "error", "message": "SIMPLIFIED TEST - Could not resolve hostname."}), 503
111
+ except OSError as e: # Specifically catch OSError for "Network is unreachable"
112
+ if e.errno == 101: # Network is unreachable
113
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - OSError [Errno 101] Network is unreachable. {e}"
114
+ print(error_msg); traceback.print_exc()
115
+ return jsonify({"status": "error", "message": "SIMPLIFIED TEST - Network is unreachable from API server."}), 503
116
+ else:
117
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - An OS error occurred: {e}"
118
+ print(error_msg); traceback.print_exc()
119
+ return jsonify({"status": "error", "message": f"SIMPLIFIED TEST - OS Error: {type(e).__name__}"}), 500
120
+ except Exception as e:
121
+ error_msg = f"EMAIL_SENDER_API (HF) [{request_id}]: SIMPLIFIED TEST - An unexpected error: {e}"
122
  print(error_msg)
123
+ traceback.print_exc()
124
+ return jsonify({"status": "error", "message": f"SIMPLIFIED TEST - Unexpected API error: {type(e).__name__}"}), 500
125
 
126
+ # The if __name__ == '__main__': block for local testing can remain commented out or as is
 
127
  # if __name__ == '__main__':
128
+ # # ... local testing startup ...