diff --git a/app/__main__.py b/app/__main__.py index 98b8519c..7b1e5a19 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -90,13 +90,7 @@ def _create_complete_application( expired_at=datetime.utcnow() + timedelta(days=app_settings.application_expiration_days), ) - message_id = mail.send_invitation_email(aws.ses_client, application) - models.Message.create( - session, - application=application, - type=models.MessageType.BORROWER_INVITATION, - external_message_id=message_id, - ) + mail.send(session, aws.ses_client, models.MessageType.BORROWER_INVITATION, application) session.commit() @@ -184,13 +178,7 @@ def send_reminders() -> None: if not state["quiet"]: print(f"Sending {len(pending_introduction_reminder)} BORROWER_PENDING_APPLICATION_REMINDER...") for application in pending_introduction_reminder: - message_id = mail.send_mail_intro_reminder(aws.ses_client, application) - models.Message.create( - session, - application=application, - type=models.MessageType.BORROWER_PENDING_APPLICATION_REMINDER, - external_message_id=message_id, - ) + mail.send(session, aws.ses_client, models.MessageType.BORROWER_PENDING_APPLICATION_REMINDER, application) session.commit() @@ -205,13 +193,7 @@ def send_reminders() -> None: if not state["quiet"]: print(f"Sending {len(pending_submission_reminder)} BORROWER_PENDING_SUBMIT_REMINDER...") for application in pending_submission_reminder: - message_id = mail.send_mail_submit_reminder(aws.ses_client, application) - models.Message.create( - session, - application=application, - type=models.MessageType.BORROWER_PENDING_SUBMIT_REMINDER, - external_message_id=message_id, - ) + mail.send(session, aws.ses_client, models.MessageType.BORROWER_PENDING_SUBMIT_REMINDER, application) session.commit() @@ -256,28 +238,15 @@ def sla_overdue_applications() -> None: if days_passed > application.lender.sla_days: application.overdued_at = datetime.now(application.created_at.tzinfo) - message_id = mail.send_overdue_application_email_to_ocp(aws.ses_client, application) - models.Message.create( - session, - application=application, - type=models.MessageType.OVERDUE_APPLICATION, - external_message_id=message_id, - ) + mail.send(session, aws.ses_client, models.MessageType.OVERDUE_APPLICATION, application) session.commit() for lender_id, lender_data in overdue_lenders.items(): - message_id = mail.send_overdue_application_email_to_lender( + mail.send_overdue_application_to_lender( aws.ses_client, - models.Lender.get(session, id=lender_id), - lender_data["count"], - ) - models.Message.create( - session, - # NOTE: A random application that might not even be to the lender, but application is not nullable. - application=application, - type=models.MessageType.OVERDUE_APPLICATION, - external_message_id=message_id, + lender=models.Lender.get(session, id=lender_id), + amount=lender_data["count"], ) session.commit() diff --git a/app/mail.py b/app/mail.py index 616d990d..ce8499da 100644 --- a/app/mail.py +++ b/app/mail.py @@ -1,145 +1,258 @@ import json import logging -import os from pathlib import Path from typing import Any from urllib.parse import quote from mypy_boto3_ses.client import SESClient +from sqlalchemy.orm import Session from app.i18n import _ -from app.models import Application, Lender, MessageType +from app.models import Application, Lender, Message, MessageType from app.settings import app_settings logger = logging.getLogger(__name__) -BASE_TEMPLATES_PATH = os.path.join(Path(__file__).absolute().parent.parent, "email_templates") +BASE_TEMPLATES_PATH = Path(__file__).absolute().parent.parent / "email_templates" -def get_template_data(template_name: str, subject: str, parameters: dict[str, Any]) -> dict[str, str]: - """ - Read the HTML file and replace its parameters (like ``BUYER_NAME``) to use as the ``{{CONTENT}}`` tag in the email - template, then return all tags required by the email template. - """ - with open( - os.path.join( - BASE_TEMPLATES_PATH, f"{template_name}{'_es' if app_settings.email_template_lang == 'es' else ''}.html" - ), - encoding="utf-8", - ) as f: - html = f.read() +def send( + session: Session, + ses: SESClient, + message_type: str, + application: Application, + *, + save: bool = True, + save_kwargs: dict[str, Any] | None = None, + **send_kwargs: Any, +) -> None: + # `template_name` can be overridden by the match statement, if it is conditional on `send_kwargs`. + # If so, use new template names for each condition. + template_name = message_type.lower() - parameters.setdefault("IMAGES_BASE_URL", app_settings.images_base_url) - for key, value in parameters.items(): - html = html.replace("{{%s}}" % key, str(value)) + base_application_url = f"{app_settings.frontend_url}/application/{quote(application.uuid)}" + + # This match statement must set `recipients`, `subject` and `parameters`. + # + # `recipients` is a list of lists. Each sublist is a `ToAddresses` parameter for an email message. + # + # All URLs using `app_settings.frontend_url` are React routes in credere-frontend. + match message_type: + case MessageType.BORROWER_INVITATION | MessageType.BORROWER_PENDING_APPLICATION_REMINDER: + base_fathom_url = "?utm_source=credere-intro&utm_medium=email&utm_campaign=" + + recipients = [[application.primary_email]] + subject = _("Opportunity to access MSME credit for being awarded a public contract") + parameters = { + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + "TENDER_TITLE": application.award.title, + "BUYER_NAME": application.award.buyer_name, + "FIND_OUT_MORE_URL": f"{base_application_url}/intro{base_fathom_url}intro", + "REMOVE_ME_URL": f"{base_application_url}/decline{base_fathom_url}decline", + } + + case MessageType.BORROWER_PENDING_SUBMIT_REMINDER: + recipients = [[application.primary_email]] + subject = _("Reminder - Opportunity to access MSME credit for being awarded a public contract") + parameters = { + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + "TENDER_TITLE": application.award.title, + "BUYER_NAME": application.award.buyer_name, + "APPLY_FOR_CREDIT_URL": f"{base_application_url}/intro", + "REMOVE_ME_URL": f"{base_application_url}/decline", + } + + case MessageType.SUBMISSION_COMPLETED: + recipients = [[application.primary_email]] + subject = _("Application Submission Complete") + parameters = { + "LENDER_NAME": application.lender.name, + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + } + + case MessageType.NEW_APPLICATION_OCP: + recipients = [[app_settings.ocp_email_group]] + subject = _("New application submission") + parameters = {"LOGIN_URL": f"{app_settings.frontend_url}/login", "LENDER_NAME": application.lender.name} + + case MessageType.NEW_APPLICATION_FI: + recipients = [_get_lender_emails(application.lender, MessageType.NEW_APPLICATION_FI)] + subject = _("New application submission") + parameters = {"LOGIN_URL": f"{app_settings.frontend_url}/login"} + + case MessageType.FI_MESSAGE: + recipients = [[application.primary_email]] + subject = _("New message from a financial institution") + parameters = { + "LENDER_NAME": application.lender.name, + "LENDER_MESSAGE": send_kwargs["message"], + "LOGIN_DOCUMENTS_URL": f"{base_application_url}/documents", + } + + case MessageType.BORROWER_DOCUMENT_UPDATED: + recipients = [_get_lender_emails(application.lender, MessageType.BORROWER_DOCUMENT_UPDATED)] + subject = _("Application updated") + parameters = {"LOGIN_URL": f"{app_settings.frontend_url}/login"} + + case MessageType.REJECTED_APPLICATION: + recipients = [[application.primary_email]] + subject = _("Your credit application has been declined") + parameters = { + "LENDER_NAME": application.lender.name, + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + } + + if send_kwargs["options"]: + template_name = "rejected_application_alternatives" + parameters["FIND_ALTENATIVE_URL"] = f"{base_application_url}/find-alternative-credit" + else: + template_name = "rejected_application_no_alternatives" + + case MessageType.APPROVED_APPLICATION: + if application.lender.default_pre_approval_message: + additional_comments = application.lender.default_pre_approval_message + elif application.lender_approved_data.get("additional_comments"): + additional_comments = application.lender_approved_data["additional_comments"] + else: + additional_comments = "Ninguno" + + recipients = [[application.primary_email]] + subject = _("Your credit application has been prequalified") + parameters = { + "LENDER_NAME": application.lender.name, + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + "TENDER_TITLE": application.award.title, + "BUYER_NAME": application.award.buyer_name, + "ADDITIONAL_COMMENTS": additional_comments, + "UPLOAD_CONTRACT_URL": f"{base_application_url}/upload-contract", + } + + case MessageType.CONTRACT_UPLOAD_CONFIRMATION: + recipients = [[application.primary_email]] + subject = _("Thank you for uploading the signed contract") + parameters = { + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + "TENDER_TITLE": application.award.title, + "BUYER_NAME": application.award.buyer_name, + } - return { - "CONTENT": html, - "SUBJECT": f"Credere - {subject}", - "FRONTEND_URL": app_settings.frontend_url, - "IMAGES_BASE_URL": app_settings.images_base_url, - } + case MessageType.CONTRACT_UPLOAD_CONFIRMATION_TO_FI: + recipients = [_get_lender_emails(application.lender, MessageType.CONTRACT_UPLOAD_CONFIRMATION_TO_FI)] + subject = _("New contract submission") + parameters = {"LOGIN_URL": f"{app_settings.frontend_url}/login"} + case MessageType.CREDIT_DISBURSED: + recipients = [[application.primary_email]] + subject = _("Your credit application has been approved") + parameters = { + "LENDER_NAME": application.lender.name, + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + "LENDER_EMAIL": application.lender.email_group, + } -def send_email(ses: SESClient, emails: list[str], data: dict[str, str], *, to_borrower: bool = True) -> str: - if app_settings.environment == "production" or not to_borrower: - to_addresses = emails - else: + case MessageType.OVERDUE_APPLICATION: + recipients = [[app_settings.ocp_email_group]] + subject = _("New overdue application") + parameters = { + "USER": application.lender.name, + "LENDER_NAME": application.lender.name, + "LOGIN_URL": f"{app_settings.frontend_url}/login", + } + + case MessageType.APPLICATION_COPIED: + recipients = [[application.primary_email]] + subject = _("Alternative credit option") + parameters = { + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + "CONTINUE_URL": f"{app_settings.frontend_url}/application/{application.uuid}/credit-options", + } + + case MessageType.EMAIL_CHANGE_CONFIRMATION: + recipients = [ + [application.primary_email], + [send_kwargs["new_email"]], + ] + subject = _("Confirm email address change") + parameters = { + "NEW_MAIL": send_kwargs["new_email"], + "AWARD_SUPPLIER_NAME": application.borrower.legal_name, + "CONFIRM_EMAIL_CHANGE_URL": ( + f"{base_application_url}/change-primary-email" + f"?token={quote(send_kwargs['confirmation_email_token'])}" + ), + } + + case _: + raise NotImplementedError + + # If at least one email address is the borrower's, assume all are the borrower's. + to_borrower = [application.primary_email] in recipients + + # Only the last message ID is saved, if multiple email messages are sent. + for to_addresses in recipients: + message_id = _send_email( + ses, + to_addresses=to_addresses, + to_borrower=to_borrower, + subject=subject, + template_name=template_name, + parameters=parameters, + ) + + if save: + if save_kwargs is None: + save_kwargs = {} + Message.create( + session, application=application, type=message_type, external_message_id=message_id, **save_kwargs + ) + + +def _send_email( + ses: SESClient, + *, + to_addresses: list[str], + to_borrower: bool = True, + subject: str, + template_name: str, + parameters: dict[str, str], +) -> str: + original_addresses = to_addresses.copy() + + if app_settings.environment != "production" and to_borrower: to_addresses = [app_settings.test_mail_receiver] + if not to_addresses: logger.error("No email address provided!") # ideally, it should be impossible for a lender to have no users return "" - logger.info("%s - Email to: %s sent to %s", app_settings.environment, emails, to_addresses) + + # Read the HTML template and replace its parameters (like ``BUYER_NAME``). + parameters.setdefault("IMAGES_BASE_URL", app_settings.images_base_url) + content = (BASE_TEMPLATES_PATH / f"{template_name}.{app_settings.email_template_lang}.html").read_text() + for key, value in parameters.items(): + content = content.replace("{{%s}}" % key, value) + + logger.info("%s - Email to: %s sent to %s", app_settings.environment, original_addresses, to_addresses) return ses.send_templated_email( Source=app_settings.email_sender_address, Destination={"ToAddresses": to_addresses}, ReplyToAddresses=[app_settings.ocp_email_group], Template=f"credere-main-{app_settings.email_template_lang}", - TemplateData=json.dumps(data), - )["MessageId"] - - -def get_lender_emails(lender: Lender, message_type: MessageType) -> list[str]: - return [user.email for user in lender.users if user.notification_preferences.get(message_type)] - - -def send_application_approved_email(ses: SESClient, application: Application) -> str: - """ - Sends an email notification when an application has been approved. - - This function generates an email message with the application details and a - link to upload the contract. The email is sent to the primary email address associated - with the application. The function utilizes the SES (Simple Email Service) client to send the email. - """ - parameters = { - "LENDER_NAME": application.lender.name, - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "TENDER_TITLE": application.award.title, - "BUYER_NAME": application.award.buyer_name, - "UPLOAD_CONTRACT_URL": f"{app_settings.frontend_url}/application/{quote(application.uuid)}/upload-contract", - } - - if application.lender.default_pre_approval_message: - parameters["ADDITIONAL_COMMENTS"] = application.lender.default_pre_approval_message - elif ( - "additional_comments" in application.lender_approved_data - and application.lender_approved_data["additional_comments"] - ): - parameters["ADDITIONAL_COMMENTS"] = application.lender_approved_data["additional_comments"] - else: - parameters["ADDITIONAL_COMMENTS"] = "Ninguno" - - return send_email( - ses, - [application.primary_email], - get_template_data("Application_approved", _("Your credit application has been prequalified"), parameters), - ) - - -def send_application_submission_completed(ses: SESClient, application: Application) -> str: - """ - Sends an email notification when an application is submitted. - - The email is sent to the primary email address associated - with the application. The function utilizes the SES (Simple Email Service) client to send the email. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Application_submitted", - _("Application Submission Complete"), + TemplateData=json.dumps( { - "LENDER_NAME": application.lender.name, - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - }, + "SUBJECT": f"Credere - {subject}", + "CONTENT": content, + "FRONTEND_URL": app_settings.frontend_url, + "IMAGES_BASE_URL": app_settings.images_base_url, + } ), - ) - + )["MessageId"] -def send_application_credit_disbursed(ses: SESClient, application: Application) -> str: - """ - Sends an email notification when an application has the credit dibursed. - The email is sent to the primary email address associated - with the application. The function utilizes the SES (Simple Email Service) client to send the email. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Application_credit_disbursed", - _("Your credit application has been approved"), - { - "LENDER_NAME": application.lender.name, - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "LENDER_EMAIL": application.lender.email_group, - }, - ), - ) +def _get_lender_emails(lender: Lender, message_type: MessageType) -> list[str]: + return [user.email for user in lender.users if user.notification_preferences.get(message_type)] -def send_mail_to_new_user(ses: SESClient, name: str, username: str, temporary_password: str) -> str: +def send_new_user(ses: SESClient, *, name: str, username: str, temporary_password: str) -> str: """ Sends an email to a new user with a link to set their password. @@ -151,98 +264,23 @@ def send_mail_to_new_user(ses: SESClient, name: str, username: str, temporary_pa :param username: The username (email address) of the new user. :param temporary_password: The temporary password for the new user. """ - return send_email( + return _send_email( ses, - [username], - get_template_data( - "New_Account_Created", - _("Welcome"), - { - "USER": name, - "LOGIN_URL": ( - f"{app_settings.frontend_url}/create-password" - f"?key={quote(temporary_password)}&email={quote(username)}" - ), - }, - ), - to_borrower=False, - ) - - -def send_upload_contract_notification_to_lender(ses: SESClient, application: Application) -> str: - """ - Sends an email to the lender to notify them of a new contract submission. - - This function generates an email message for the lender associated with - the application, notifying them that a new contract has been submitted and needs their review. - The email contains a link to login and review the contract. - """ - return send_email( - ses, - get_lender_emails(application.lender, MessageType.CONTRACT_UPLOAD_CONFIRMATION_TO_FI), - get_template_data( - "New_contract_submission", - _("New contract submission"), - { - "LOGIN_URL": f"{app_settings.frontend_url}/login", - }, - ), + to_addresses=[username], to_borrower=False, - ) - - -def send_upload_contract_confirmation(ses: SESClient, application: Application) -> str: - """ - Sends an email to the borrower confirming the successful upload of the contract. - - This function generates an email message for the borrower associated with the application, - confirming that their contract has been successfully uploaded. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Contract_upload_confirmation", - _("Thank you for uploading the signed contract"), - { - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "TENDER_TITLE": application.award.title, - "BUYER_NAME": application.award.buyer_name, - }, - ), - ) - - -def send_new_email_confirmation( - ses: SESClient, application: Application, new_email: str, confirmation_email_token: str -) -> str: - """ - Sends an email to confirm the new primary email for the borrower. - - This function generates and sends an email message to the new and old email addresses, - providing a link for the user to confirm the email change. - - :param new_email: The new email address to be set as the primary email. - :param confirmation_email_token: The token generated for confirming the email change. - """ - data = get_template_data( - "Confirm_email_address_change", - _("Confirm email address change"), - { - "NEW_MAIL": new_email, - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "CONFIRM_EMAIL_CHANGE_URL": ( - f"{app_settings.frontend_url}/application/{quote(application.uuid)}/change-primary-email" - f"?token={quote(confirmation_email_token)}" + subject=_("Welcome"), + template_name="new_user", + parameters={ + "USER": name, + "LOGIN_URL": ( + f"{app_settings.frontend_url}/create-password" + f"?key={quote(temporary_password)}&email={quote(username)}" ), }, ) - send_email(ses, [application.primary_email], data) - return send_email(ses, [new_email], data) - -def send_mail_to_reset_password(ses: SESClient, username: str, temporary_password: str) -> str: +def send_reset_password(ses: SESClient, *, username: str, temporary_password: str) -> str: """ Sends an email to a user with instructions to reset their password. @@ -252,272 +290,38 @@ def send_mail_to_reset_password(ses: SESClient, username: str, temporary_passwor :param username: The username associated with the account for which the password is to be reset. :param temporary_password: A temporary password generated for the account. """ - return send_email( + return _send_email( ses, - [username], - get_template_data( - "Reset_password", - _("Reset password"), - { - "USER_ACCOUNT": username, - "RESET_PASSWORD_URL": ( - f"{app_settings.frontend_url}/create-password" - f"?key={quote(temporary_password)}&email={quote(username)}" - ), - }, - ), + to_addresses=[username], to_borrower=False, + subject=_("Reset password"), + template_name="reset_password", + parameters={ + "USER_ACCOUNT": username, + "RESET_PASSWORD_URL": ( + f"{app_settings.frontend_url}/create-password" + f"?key={quote(temporary_password)}&email={quote(username)}" + ), + }, ) -def get_invitation_email_parameters(application: Application) -> dict[str, str]: - base_application_url = f"{app_settings.frontend_url}/application/{quote(application.uuid)}" - base_fathom_url = "?utm_source=credere-intro&utm_medium=email&utm_campaign=" - return { - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "TENDER_TITLE": application.award.title, - "BUYER_NAME": application.award.buyer_name, - "FIND_OUT_MORE_URL": f"{base_application_url}/intro{base_fathom_url}intro", - "REMOVE_ME_URL": f"{base_application_url}/decline{base_fathom_url}decline", - } - - -def send_invitation_email(ses: SESClient, application: Application) -> str: - """ - Sends an invitation email to the provided email address. - - This function sends an email containing an invitation to the recipient to join a credit scheme. - It also provides options to find out more or to decline the invitation. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Access_to_credit_scheme_for_MSMEs", - _("Opportunity to access MSME credit for being awarded a public contract"), - get_invitation_email_parameters(application), - ), - ) - - -def send_mail_intro_reminder(ses: SESClient, application: Application) -> str: - """ - Sends an introductory reminder email to the provided email address. - - This function sends a reminder email to the recipient about an invitation to join a credit scheme. - The email also provides options to find out more or to decline the invitation. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Access_to_credit_reminder", - _("Opportunity to access MSME credit for being awarded a public contract"), - get_invitation_email_parameters(application), - ), - ) - - -def send_mail_submit_reminder(ses: SESClient, application: Application) -> str: - """ - Sends a submission reminder email to the provided email address. - - This function sends a reminder email to the recipient about a pending credit scheme application. - The email also provides options to apply for the credit or to decline the application. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Complete_application_reminder", - _("Reminder - Opportunity to access MSME credit for being awarded a public contract"), - { - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "TENDER_TITLE": application.award.title, - "BUYER_NAME": application.award.buyer_name, - "APPLY_FOR_CREDIT_URL": f"{app_settings.frontend_url}/application/{quote(application.uuid)}/intro", - "REMOVE_ME_URL": f"{app_settings.frontend_url}/application/{quote(application.uuid)}/decline", - }, - ), - ) - - -def send_notification_new_app_to_lender(ses: SESClient, lender: Lender) -> str: - """ - Sends a notification email about a new application to a lender's email group. - - :param lender: The lender to email. - """ - return send_email( - ses, - get_lender_emails(lender, MessageType.NEW_APPLICATION_FI), - get_template_data( - "FI_New_application_submission_FI_user", - _("New application submission"), - { - "LOGIN_URL": f"{app_settings.frontend_url}/login", - }, - ), - to_borrower=False, - ) - - -def send_notification_new_app_to_ocp(ses: SESClient, application: Application) -> str: - """ - Sends a notification email about a new application to the Open Contracting Partnership's (OCP) email group. - """ - return send_email( - ses, - [app_settings.ocp_email_group], - get_template_data( - "New_application_submission_OCP_user", - _("New application submission"), - { - "LENDER_NAME": application.lender.name, - "LOGIN_URL": f"{app_settings.frontend_url}/login", - }, - ), - to_borrower=False, - ) - - -def send_mail_request_to_borrower(ses: SESClient, application: Application, email_message: str) -> str: - """ - Sends an email request to the borrower for additional data. - - :param email_message: Message content from the lender to be included in the email. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Request_data_to_SME", - _("New message from a financial institution"), - { - "LENDER_NAME": application.lender.name, - "LENDER_MESSAGE": email_message, - "LOGIN_DOCUMENTS_URL": f"{app_settings.frontend_url}/application/{quote(application.uuid)}/documents", - }, - ), - ) - - -def send_overdue_application_email_to_lender(ses: SESClient, lender: Lender, amount: int) -> str: +def send_overdue_application_to_lender(ses: SESClient, *, lender: Lender, amount: int) -> str: """ Sends an email notification to the lender about overdue applications. :param lender: The overdue lender. :param amount: Number of overdue applications. """ - return send_email( - ses, - get_lender_emails(lender, MessageType.OVERDUE_APPLICATION), - get_template_data( - "Overdue_application_FI", - _("You have credit applications that need processing"), - { - "USER": lender.name, - "NUMBER_APPLICATIONS": amount, - "LOGIN_URL": f"{app_settings.frontend_url}/login", - }, - ), - to_borrower=False, - ) - - -def send_overdue_application_email_to_ocp(ses: SESClient, application: Application) -> str: - """ - Sends an email notification to the Open Contracting Partnership (OCP) about overdue applications. - """ - return send_email( - ses, - [app_settings.ocp_email_group], - get_template_data( - "Overdue_application_OCP_admin", - _("New overdue application"), - { - "USER": application.lender.name, - "LENDER_NAME": application.lender.name, - "LOGIN_URL": f"{app_settings.frontend_url}/login", - }, - ), - to_borrower=False, - ) - - -def send_rejected_application_email(ses: SESClient, application: Application) -> str: - """ - Sends an email notification to the applicant when an application has been rejected. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Application_declined", - _("Your credit application has been declined"), - { - "LENDER_NAME": application.lender.name, - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "FIND_ALTENATIVE_URL": ( - f"{app_settings.frontend_url}/application/{quote(application.uuid)}/find-alternative-credit" - ), - }, - ), - ) - - -def send_rejected_application_email_without_alternatives(ses: SESClient, application: Application) -> str: - """ - Sends an email notification to the applicant when an application has been rejected, - and no alternatives are available. - """ - return send_email( - ses, - [application.primary_email], - get_template_data( - "Application_declined_without_alternative", - _("Your credit application has been declined"), - { - "LENDER_NAME": application.lender.name, - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - }, - ), - ) - - -def send_copied_application_notification_to_borrower(ses: SESClient, application: Application) -> str: - """ - Sends an email notification to the borrower when an application - has been copied, allowing them to continue with the application process. - """ - return send_email( + return _send_email( ses, - [application.primary_email], - get_template_data( - "alternative_credit_msme", - _("Alternative credit option"), - { - "AWARD_SUPPLIER_NAME": application.borrower.legal_name, - "CONTINUE_URL": f"{app_settings.frontend_url}/application/{application.uuid}/credit-options", - }, - ), - ) - - -def send_upload_documents_notifications_to_lender(ses: SESClient, application: Application) -> str: - """ - Sends an email notification to the lender to notify them that new - documents have been uploaded and are ready for their review. - """ - return send_email( - ses, - get_lender_emails(application.lender, MessageType.BORROWER_DOCUMENT_UPDATED), - get_template_data( - "FI_Documents_Updated_FI_user", - _("Application updated"), - { - "LOGIN_URL": f"{app_settings.frontend_url}/login", - }, - ), + to_addresses=_get_lender_emails(lender, MessageType.OVERDUE_APPLICATION), to_borrower=False, + subject=_("You have credit applications that need processing"), + template_name="overdue_application_to_lender", + parameters={ + "USER": lender.name, + "NUMBER_APPLICATIONS": str(amount), + "LOGIN_URL": f"{app_settings.frontend_url}/login", + }, ) diff --git a/app/routers/applications.py b/app/routers/applications.py index 6d6b405f..a379f78b 100644 --- a/app/routers/applications.py +++ b/app/routers/applications.py @@ -45,7 +45,7 @@ async def reject_application( payload_dict = jsonable_encoder(payload, exclude_unset=True) application.stage_as_rejected(payload_dict) - options = ( + options = session.query( session.query(models.CreditProduct) .join(models.Lender) .options(joinedload(models.CreditProduct.lender)) @@ -55,8 +55,8 @@ async def reject_application( col(models.CreditProduct.lower_limit) <= application.amount_requested, col(models.CreditProduct.upper_limit) >= application.amount_requested, ) - .all() - ) + .exists() + ).scalar() models.ApplicationAction.create( session, @@ -66,16 +66,7 @@ async def reject_application( user_id=user.id, ) - if options: - message_id = mail.send_rejected_application_email(client.ses, application) - else: - message_id = mail.send_rejected_application_email_without_alternatives(client.ses, application) - models.Message.create( - session, - application=application, - type=models.MessageType.REJECTED_APPLICATION, - external_message_id=message_id, - ) + mail.send(session, client.ses, models.MessageType.REJECTED_APPLICATION, application, options=options) session.commit() return application @@ -118,13 +109,7 @@ async def complete_application( user_id=user.id, ) - message_id = mail.send_application_credit_disbursed(client.ses, application) - models.Message.create( - session, - application=application, - type=models.MessageType.CREDIT_DISBURSED, - external_message_id=message_id, - ) + mail.send(session, client.ses, models.MessageType.CREDIT_DISBURSED, application) session.commit() return application @@ -196,13 +181,7 @@ async def approve_application( user_id=user.id, ) - message_id = mail.send_application_approved_email(client.ses, application) - models.Message.create( - session, - application=application, - type=models.MessageType.APPROVED_APPLICATION, - external_message_id=message_id, - ) + mail.send(session, client.ses, models.MessageType.APPROVED_APPLICATION, application) session.commit() return application @@ -551,14 +530,13 @@ async def email_borrower( user_id=user.id, ) - message_id = mail.send_mail_request_to_borrower(client.ses, application, payload.message) - models.Message.create( + mail.send( session, - application_id=application.id, - body=payload.message, - lender_id=application.lender.id, - type=models.MessageType.FI_MESSAGE, - external_message_id=message_id, + client.ses, + models.MessageType.FI_MESSAGE, + application, + message=payload.message, + save_kwargs={"body": payload.message, "lender_id": application.lender.id}, ) session.commit() diff --git a/app/routers/guest/applications.py b/app/routers/guest/applications.py index d783f089..ee09c6f8 100644 --- a/app/routers/guest/applications.py +++ b/app/routers/guest/applications.py @@ -532,16 +532,9 @@ async def update_apps_send_notifications( application.borrower_submitted_at = datetime.now(application.created_at.tzinfo) application.pending_documents = False - mail.send_notification_new_app_to_lender(client.ses, application.lender) - mail.send_notification_new_app_to_ocp(client.ses, application) - - message_id = mail.send_application_submission_completed(client.ses, application) - models.Message.create( - session, - application=application, - type=models.MessageType.SUBMISSION_COMPLETED, - external_message_id=message_id, - ) + mail.send(session, client.ses, models.MessageType.NEW_APPLICATION_OCP, application, save=False) + mail.send(session, client.ses, models.MessageType.NEW_APPLICATION_FI, application, save=False) + mail.send(session, client.ses, models.MessageType.SUBMISSION_COMPLETED, application) session.commit() return serializers.ApplicationResponse( @@ -637,13 +630,7 @@ async def complete_information_request( application_id=application.id, ) - message_id = mail.send_upload_documents_notifications_to_lender(client.ses, application) - models.Message.create( - session, - application=application, - type=models.MessageType.BORROWER_DOCUMENT_UPDATED, - external_message_id=message_id, - ) + mail.send(session, client.ses, models.MessageType.BORROWER_DOCUMENT_UPDATED, application) session.commit() return serializers.ApplicationResponse( @@ -723,22 +710,8 @@ async def confirm_upload_contract( application_id=application.id, ) - lender_message_id, borrower_message_id = ( - mail.send_upload_contract_notification_to_lender(client.ses, application), - mail.send_upload_contract_confirmation(client.ses, application), - ) - models.Message.create( - session, - application=application, - type=models.MessageType.CONTRACT_UPLOAD_CONFIRMATION_TO_FI, - external_message_id=lender_message_id, - ) - models.Message.create( - session, - application=application, - type=models.MessageType.CONTRACT_UPLOAD_CONFIRMATION, - external_message_id=borrower_message_id, - ) + mail.send(session, client.ses, models.MessageType.CONTRACT_UPLOAD_CONFIRMATION_TO_FI, application) + mail.send(session, client.ses, models.MessageType.CONTRACT_UPLOAD_CONFIRMATION, application) session.commit() return serializers.ApplicationResponse( @@ -819,13 +792,7 @@ async def find_alternative_credit_option( application_id=new_application.id, ) - message_id = mail.send_copied_application_notification_to_borrower(client.ses, new_application) - models.Message.create( - session, - application=new_application, - type=models.MessageType.APPLICATION_COPIED, - external_message_id=message_id, - ) + mail.send(session, client.ses, models.MessageType.APPLICATION_COPIED, new_application) session.commit() return serializers.ApplicationResponse( diff --git a/app/routers/guest/emails.py b/app/routers/guest/emails.py index aba55140..3cc9018b 100644 --- a/app/routers/guest/emails.py +++ b/app/routers/guest/emails.py @@ -45,14 +45,13 @@ async def change_email( application_id=application.id, ) - message_id = mail.send_new_email_confirmation( - client.ses, application, payload.new_email, confirmation_email_token - ) - models.Message.create( + mail.send( session, - application=application, - type=models.MessageType.EMAIL_CHANGE_CONFIRMATION, - external_message_id=message_id, + client.ses, + models.MessageType.EMAIL_CHANGE_CONFIRMATION, + application, + new_email=payload.new_email, + confirmation_email_token=confirmation_email_token, ) session.commit() diff --git a/app/routers/users.py b/app/routers/users.py index ab992536..4cf0e9c4 100644 --- a/app/routers/users.py +++ b/app/routers/users.py @@ -47,7 +47,9 @@ async def create_user( session.commit() - mail.send_mail_to_new_user(client.ses, payload.name, payload.email, temporary_password) + mail.send_new_user( + client.ses, name=payload.name, username=payload.email, temporary_password=temporary_password + ) return user except (client.cognito.exceptions.UsernameExistsException, IntegrityError): @@ -258,7 +260,7 @@ def forgot_password( Permanent=False, ) - mail.send_mail_to_reset_password(client.ses, payload.username, temporary_password) + mail.send_reset_password(client.ses, username=payload.username, temporary_password=temporary_password) # always return 200 to avoid user enumeration return serializers.ResponseBase(detail=_("An email with a reset link was sent to end user")) diff --git a/email_templates/__init__.py b/email_templates/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/email_templates/alternative_credit_msme.html b/email_templates/application_copied.en.html similarity index 100% rename from email_templates/alternative_credit_msme.html rename to email_templates/application_copied.en.html diff --git a/email_templates/alternative_credit_msme_es.html b/email_templates/application_copied.es.html similarity index 100% rename from email_templates/alternative_credit_msme_es.html rename to email_templates/application_copied.es.html diff --git a/email_templates/Application_approved.html b/email_templates/approved_application.en.html similarity index 100% rename from email_templates/Application_approved.html rename to email_templates/approved_application.en.html diff --git a/email_templates/Application_approved_es.html b/email_templates/approved_application.es.html similarity index 100% rename from email_templates/Application_approved_es.html rename to email_templates/approved_application.es.html diff --git a/email_templates/FI_Documents_Updated_FI_user.html b/email_templates/borrower_document_updated.en.html similarity index 100% rename from email_templates/FI_Documents_Updated_FI_user.html rename to email_templates/borrower_document_updated.en.html diff --git a/email_templates/FI_Documents_Updated_FI_user_es.html b/email_templates/borrower_document_updated.es.html similarity index 100% rename from email_templates/FI_Documents_Updated_FI_user_es.html rename to email_templates/borrower_document_updated.es.html diff --git a/email_templates/Access_to_credit_scheme_for_MSMEs.html b/email_templates/borrower_invitation.en.html similarity index 100% rename from email_templates/Access_to_credit_scheme_for_MSMEs.html rename to email_templates/borrower_invitation.en.html diff --git a/email_templates/Access_to_credit_scheme_for_MSMEs_es.html b/email_templates/borrower_invitation.es.html similarity index 100% rename from email_templates/Access_to_credit_scheme_for_MSMEs_es.html rename to email_templates/borrower_invitation.es.html diff --git a/email_templates/Access_to_credit_reminder.html b/email_templates/borrower_pending_application_reminder.en.html similarity index 100% rename from email_templates/Access_to_credit_reminder.html rename to email_templates/borrower_pending_application_reminder.en.html diff --git a/email_templates/Access_to_credit_reminder_es.html b/email_templates/borrower_pending_application_reminder.es.html similarity index 100% rename from email_templates/Access_to_credit_reminder_es.html rename to email_templates/borrower_pending_application_reminder.es.html diff --git a/email_templates/Complete_application_reminder.html b/email_templates/borrower_pending_submit_reminder.en.html similarity index 100% rename from email_templates/Complete_application_reminder.html rename to email_templates/borrower_pending_submit_reminder.en.html diff --git a/email_templates/Complete_application_reminder_es.html b/email_templates/borrower_pending_submit_reminder.es.html similarity index 100% rename from email_templates/Complete_application_reminder_es.html rename to email_templates/borrower_pending_submit_reminder.es.html diff --git a/email_templates/Contract_upload_confirmation.html b/email_templates/contract_upload_confirmation.en.html similarity index 100% rename from email_templates/Contract_upload_confirmation.html rename to email_templates/contract_upload_confirmation.en.html diff --git a/email_templates/Contract_upload_confirmation_es.html b/email_templates/contract_upload_confirmation.es.html similarity index 100% rename from email_templates/Contract_upload_confirmation_es.html rename to email_templates/contract_upload_confirmation.es.html diff --git a/email_templates/New_contract_submission.html b/email_templates/contract_upload_confirmation_to_fi.en.html similarity index 100% rename from email_templates/New_contract_submission.html rename to email_templates/contract_upload_confirmation_to_fi.en.html diff --git a/email_templates/New_contract_submission_es.html b/email_templates/contract_upload_confirmation_to_fi.es.html similarity index 100% rename from email_templates/New_contract_submission_es.html rename to email_templates/contract_upload_confirmation_to_fi.es.html diff --git a/email_templates/Application_credit_disbursed.html b/email_templates/credit_disbursed.en.html similarity index 100% rename from email_templates/Application_credit_disbursed.html rename to email_templates/credit_disbursed.en.html diff --git a/email_templates/Application_credit_disbursed_es.html b/email_templates/credit_disbursed.es.html similarity index 100% rename from email_templates/Application_credit_disbursed_es.html rename to email_templates/credit_disbursed.es.html diff --git a/email_templates/Confirm_email_address_change.html b/email_templates/email_change_confirmation.en.html similarity index 100% rename from email_templates/Confirm_email_address_change.html rename to email_templates/email_change_confirmation.en.html diff --git a/email_templates/Confirm_email_address_change_es.html b/email_templates/email_change_confirmation.es.html similarity index 100% rename from email_templates/Confirm_email_address_change_es.html rename to email_templates/email_change_confirmation.es.html diff --git a/email_templates/Request_data_to_SME.html b/email_templates/fi_message.en.html similarity index 100% rename from email_templates/Request_data_to_SME.html rename to email_templates/fi_message.en.html diff --git a/email_templates/Request_data_to_SME_es.html b/email_templates/fi_message.es.html similarity index 100% rename from email_templates/Request_data_to_SME_es.html rename to email_templates/fi_message.es.html diff --git a/email_templates/FI_New_application_submission_FI_user.html b/email_templates/new_application_fi.en.html similarity index 100% rename from email_templates/FI_New_application_submission_FI_user.html rename to email_templates/new_application_fi.en.html diff --git a/email_templates/FI_New_application_submission_FI_user_es.html b/email_templates/new_application_fi.es.html similarity index 100% rename from email_templates/FI_New_application_submission_FI_user_es.html rename to email_templates/new_application_fi.es.html diff --git a/email_templates/New_application_submission_OCP_user.html b/email_templates/new_application_ocp.en.html similarity index 100% rename from email_templates/New_application_submission_OCP_user.html rename to email_templates/new_application_ocp.en.html diff --git a/email_templates/New_application_submission_OCP_user_es.html b/email_templates/new_application_ocp.es.html similarity index 100% rename from email_templates/New_application_submission_OCP_user_es.html rename to email_templates/new_application_ocp.es.html diff --git a/email_templates/New_Account_Created.html b/email_templates/new_user.en.html similarity index 100% rename from email_templates/New_Account_Created.html rename to email_templates/new_user.en.html diff --git a/email_templates/New_Account_Created_es.html b/email_templates/new_user.es.html similarity index 100% rename from email_templates/New_Account_Created_es.html rename to email_templates/new_user.es.html diff --git a/email_templates/Overdue_application_OCP_admin.html b/email_templates/overdue_application.en.html similarity index 100% rename from email_templates/Overdue_application_OCP_admin.html rename to email_templates/overdue_application.en.html diff --git a/email_templates/Overdue_application_OCP_admin_es.html b/email_templates/overdue_application.es.html similarity index 100% rename from email_templates/Overdue_application_OCP_admin_es.html rename to email_templates/overdue_application.es.html diff --git a/email_templates/Overdue_application_FI.html b/email_templates/overdue_application_to_lender.en.html similarity index 100% rename from email_templates/Overdue_application_FI.html rename to email_templates/overdue_application_to_lender.en.html diff --git a/email_templates/Overdue_application_FI_es.html b/email_templates/overdue_application_to_lender.es.html similarity index 100% rename from email_templates/Overdue_application_FI_es.html rename to email_templates/overdue_application_to_lender.es.html diff --git a/email_templates/Application_declined.html b/email_templates/rejected_application_alternatives.en.html similarity index 100% rename from email_templates/Application_declined.html rename to email_templates/rejected_application_alternatives.en.html diff --git a/email_templates/Application_declined_es.html b/email_templates/rejected_application_alternatives.es.html similarity index 100% rename from email_templates/Application_declined_es.html rename to email_templates/rejected_application_alternatives.es.html diff --git a/email_templates/Application_declined_without_alternative.html b/email_templates/rejected_application_no_alternatives.en.html similarity index 100% rename from email_templates/Application_declined_without_alternative.html rename to email_templates/rejected_application_no_alternatives.en.html diff --git a/email_templates/Application_declined_without_alternative_es.html b/email_templates/rejected_application_no_alternatives.es.html similarity index 100% rename from email_templates/Application_declined_without_alternative_es.html rename to email_templates/rejected_application_no_alternatives.es.html diff --git a/email_templates/Reset_password.html b/email_templates/reset_password.en.html similarity index 100% rename from email_templates/Reset_password.html rename to email_templates/reset_password.en.html diff --git a/email_templates/Reset_password_es.html b/email_templates/reset_password.es.html similarity index 100% rename from email_templates/Reset_password_es.html rename to email_templates/reset_password.es.html diff --git a/email_templates/Application_submitted.html b/email_templates/submission_completed.en.html similarity index 100% rename from email_templates/Application_submitted.html rename to email_templates/submission_completed.en.html diff --git a/email_templates/Application_submitted_es.html b/email_templates/submission_completed.es.html similarity index 100% rename from email_templates/Application_submitted_es.html rename to email_templates/submission_completed.es.html