""" app/enrollment/mailer.py ------------------------ The enrollment confirmation email. Sent to the requester after a submission is stored. One job, and it must never be able to break that: the record is already safely on disk before this runs, so every failure path here is logged and swallowed. A bounced confirmation must not cost the customer their enrollment. Sending happens on a background thread (rule 14 — never block the HTTP response), and the From identity comes from branded_sender() so it stays an SMTP-authorized address that actually delivers (rules 64 / 76). This is the only part of app/enrollment that touches shared mail infrastructure; it still imports no models and writes no DB row. """ import logging import threading from flask import current_app, render_template logger = logging.getLogger(__name__) def _text_body(record, people): """Plain-text alternative — some recipients see only this.""" lines = [ f'Hi {record.get("request_by") or "there"},', '', 'Thank you — we have received your JQC enrollment form.', '', f'Reference: {record.get("id")}', f'Project: {record.get("project_name")}', '', f'People to be set up ({len(people)}):', ] for i, person in enumerate(people, start=1): lines.append( f' {i}. {person["name"]} — {person["role_label"]} — {person["email"]}' ) lines += [ '', 'Our team will create these accounts. Each person will receive their own ' 'email invitation with sign-in instructions.', '', 'If anything above is wrong, simply reply to this email and we will ' 'correct it.', '', 'JQC by L.T Services, Inc', ] return '\n'.join(lines) def send_confirmation(record, base_url=None): """Email the requester a copy of what they submitted. Never raises.""" email = (record.get('requester_email') or '').strip() if not email: return if not current_app.config.get('MAIL_SERVER'): logger.warning('ENROLLMENT EMAIL SKIPPED | no MAIL_SERVER | id=%s', record.get('id')) return try: from flask_mail import Message from app import mail from app.utils.mail_utils import branded_sender from . import schema effective_base = (base_url or current_app.config.get('APP_BASE_URL', '')).rstrip('/') people = schema.people_of(record) msg = Message( subject = f'[JQC] Enrollment received — {record.get("project_name")}', sender = branded_sender(effective_base), recipients = [email], body = _text_body(record, people), html = render_template('enrollment/email_confirmation.html', record=record, people=people, schema=schema), ) app = current_app._get_current_object() def _send(): with app.app_context(): try: mail.send(msg) logger.info('ENROLLMENT EMAIL SENT | to=%s | id=%s', email, record.get('id')) except Exception as exc: logger.error('ENROLLMENT EMAIL FAILED | to=%s | id=%s | error=%s', email, record.get('id'), exc) threading.Thread(target=_send, daemon=True).start() except Exception: # Building the message failed (bad template, mail misconfigured, …). # The submission is already saved — log it and move on. logger.exception('ENROLLMENT EMAIL BUILD FAILED | id=%s', record.get('id'))