04/17 Update: sends email to each IT Staff from email ingestion ticket

This commit is contained in:
2026-04-17 14:24:16 -04:00
parent 104a0633f1
commit 25af7a836d
+25 -5
View File
@@ -107,18 +107,34 @@ def create_notification(user_id, notif_type, title, message, ticket_id=None, lin
def send_email(subject, recipients, html_body): def send_email(subject, recipients, html_body):
""" """
Send an email in a background thread so it never blocks the request. Send an email in a background thread so it never blocks the caller.
Flask-Mail opens an SMTP connection synchronously. Running it in the main Flask-Mail opens an SMTP connection synchronously. Running it in the main
request thread means a slow or failing SMTP server delays the entire request thread means a slow or failing SMTP server delays the entire
response — including the WebSocket notification push that follows. response — including the WebSocket notification push that follows.
Using a daemon thread isolates SMTP failures from the request lifecycle. Using a background thread isolates SMTP failures from the request lifecycle.
Thread daemon mode
------------------
When called from a web request context, the thread is spawned as a daemon
(daemon=True) so it does not prevent the server process from exiting cleanly.
When called from an APScheduler background job (no active request context),
the thread is spawned as a non-daemon (daemon=False). Daemon threads are
killed as soon as the parent thread exits — since the APScheduler job thread
finishes quickly, a daemon email thread is terminated before SMTP delivery
completes, causing silent email loss. A non-daemon thread survives until
the SMTP handshake finishes regardless of the job thread's lifetime.
""" """
from threading import Thread from threading import Thread
from flask import current_app from flask import current_app, has_request_context
app = current_app._get_current_object() # real app, not the proxy app = current_app._get_current_object() # real app, not the proxy
# Determine daemon mode based on whether we are inside a live HTTP request.
# APScheduler jobs and other background callers have no request context.
is_daemon = has_request_context()
def _send(): def _send():
with app.app_context(): with app.app_context():
try: try:
@@ -128,7 +144,7 @@ def send_email(subject, recipients, html_body):
except Exception as exc: except Exception as exc:
logger.error(f'[EMAIL ERROR] Failed to send "{subject}" to {recipients}: {exc}') logger.error(f'[EMAIL ERROR] Failed to send "{subject}" to {recipients}: {exc}')
t = Thread(target=_send, daemon=True) t = Thread(target=_send, daemon=is_daemon)
t.start() t.start()
@@ -502,6 +518,10 @@ def send_satisfaction_survey(ticket):
f'recipient={creator_email}: {exc}', exc_info=True f'recipient={creator_email}: {exc}', exc_info=True
) )
t = Thread(target=_send, daemon=True) # Mirror the daemon-mode logic from send_email(): non-daemon when called
# outside a request context (e.g. from the SLA APScheduler job) so the
# thread is not killed before SMTP delivery completes.
from flask import has_request_context
t = Thread(target=_send, daemon=has_request_context())
t.start() t.start()
logger.info(f'[SURVEY] Email thread started for ticket_id={ticket_id}') logger.info(f'[SURVEY] Email thread started for ticket_id={ticket_id}')