04/17 Update: sends email to each IT Staff from email ingestion ticket
This commit is contained in:
@@ -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}')
|
||||||
Reference in New Issue
Block a user