Aug 6 - Update enrollment page, add email notification
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
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'))
|
||||
Reference in New Issue
Block a user