nonzeroexit commited on
Commit
45a55f0
·
verified ·
1 Parent(s): 27373d6

Update email_sender_api.py

Browse files
Files changed (1) hide show
  1. email_sender_api.py +94 -28
email_sender_api.py CHANGED
@@ -7,8 +7,10 @@ from email.mime.base import MIMEBase
7
  from email import encoders
8
  import base64
9
  import os
 
 
10
 
11
- app = Flask(__name__) # Make sure your Flask app instance is named 'app'
12
  CORS(app)
13
 
14
  # Environment variables will be set as Secrets in Hugging Face Space settings
@@ -17,40 +19,45 @@ SMTP_SERVER_PORT = os.environ.get("SMTP_SERVER_PORT")
17
  SMTP_SENDER_EMAIL = os.environ.get("SMTP_SENDER_EMAIL")
18
  SMTP_SENDER_PASSWORD = os.environ.get("SMTP_SENDER_PASSWORD")
19
 
 
 
 
20
  @app.route('/send-report-via-email', methods=['POST'])
21
  def handle_send_email():
22
- print("EMAIL_SENDER_API (HF): Received request at /send-report-via-email")
 
 
23
  if not all([SMTP_SERVER_HOST, SMTP_SERVER_PORT, SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD]):
24
- error_msg = "EMAIL_SENDER_API (HF): ERROR - SMTP environment variables (Secrets) not fully set."
25
  print(error_msg)
26
  return jsonify({"status": "error", "message": "Email server configuration incomplete on the API."}), 500
27
 
28
  try:
29
  SMTP_PORT_INT = int(SMTP_SERVER_PORT)
30
  except ValueError:
31
- error_msg = "EMAIL_SENDER_API (HF): ERROR - Invalid SMTP_SERVER_PORT configured."
32
  print(error_msg)
33
  return jsonify({"status": "error", "message": "Invalid email server port configured on the API."}), 500
34
 
35
  data = request.json
36
  if not data:
37
- print("EMAIL_SENDER_API (HF): No JSON data received.")
38
  return jsonify({"status": "error", "message": "No data received by email API."}), 400
39
 
40
  recipient_email = data.get('recipient_email')
41
  pdf_base64_data = data.get('pdf_base64_data')
42
  pdf_filename = data.get('pdf_filename')
43
- print(f"EMAIL_SENDER_API (HF): Attempting to send to: {recipient_email}, Filename: {pdf_filename}")
44
 
45
  if not all([recipient_email, pdf_base64_data, pdf_filename]):
46
- print("EMAIL_SENDER_API (HF): Missing required data in JSON payload.")
47
  return jsonify({"status": "error", "message": "Missing required data: recipient_email, pdf_base64_data, or pdf_filename."}), 400
48
 
49
  try:
50
  msg = MIMEMultipart()
51
  msg['From'] = SMTP_SENDER_EMAIL
52
  msg['To'] = recipient_email
53
- msg['Subject'] = f"EPIC-AMP Analysis Report: {pdf_filename}"
54
  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]"""
55
  msg.attach(MIMEText(body, 'plain'))
56
 
@@ -61,38 +68,97 @@ def handle_send_email():
61
  part.add_header('Content-Disposition', f"attachment; filename=\"{pdf_filename}\"")
62
  msg.attach(part)
63
 
64
- print(f"EMAIL_SENDER_API (HF): Connecting to SMTP server {SMTP_SERVER_HOST}:{SMTP_PORT_INT}")
65
- with smtplib.SMTP(SMTP_SERVER_HOST, SMTP_PORT_INT) as server:
 
 
 
 
 
 
 
 
 
 
 
66
  server.set_debuglevel(1)
67
- server.ehlo()
68
- if SMTP_PORT_INT == 587:
69
- print("EMAIL_SENDER_API (HF): Starting TLS...")
 
 
70
  server.starttls()
 
71
  server.ehlo()
72
- print("EMAIL_SENDER_API (HF): Logging in...")
 
73
  server.login(SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD)
74
- print("EMAIL_SENDER_API (HF): Sending mail...")
 
75
  server.sendmail(SMTP_SENDER_EMAIL, recipient_email, msg.as_string())
76
- print("EMAIL_SENDER_API (HF): Mail sent.")
77
 
 
 
 
 
 
 
 
 
78
  success_msg = f"Report successfully sent to {recipient_email}."
79
- print(f"EMAIL_SENDER_API (HF): {success_msg}")
80
  return jsonify({"status": "success", "message": success_msg}), 200
81
 
 
 
 
 
 
 
 
 
82
  except smtplib.SMTPAuthenticationError as e:
83
- error_msg = f"EMAIL_SENDER_API (HF): SMTP Authentication Error - {e}"
84
- print(error_msg)
85
  return jsonify({"status": "error", "message": "Email server authentication failed on the API."}), 500
86
- except Exception as e:
87
- import traceback
88
- error_msg = f"EMAIL_SENDER_API (HF): Error sending email to {recipient_email} - {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  print(error_msg)
90
- print(traceback.format_exc())
91
- return jsonify({"status": "error", "message": f"Failed to send email via API: {e}"}), 500
92
 
93
- # This __main__ block is mostly for local testing.
94
  # On Hugging Face Spaces with Gunicorn, Gunicorn directly imports and runs the 'app' object.
95
  # if __name__ == '__main__':
96
- # port = int(os.environ.get("PORT", 7860)) # Default port for HF Spaces if not overridden
97
- # print(f"Starting Email Sender API Service on port {port} locally...")
98
- # app.run(host='0.0.0.0', port=port, debug=True)
 
 
 
 
 
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
 
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
34
 
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
 
 
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)