def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User): address = alias.email email_log: EmailLog = EmailLog.create(contact_id=contact.id, bounced=True, user_id=contact.user_id) db.session.commit() nb_bounced = EmailLog.filter_by(contact_id=contact.id, bounced=True).count() disable_alias_link = f"{URL}/dashboard/unsubscribe/{alias.id}" # <<< Store the bounced email >>> # generate a name for the email random_name = str(uuid.uuid4()) full_report_path = f"refused-emails/full-{random_name}.eml" s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()), random_name) orig_msg = get_orig_message_from_bounce(msg) if not orig_msg: LOG.error( "Cannot parse original message from bounce message %s %s %s %s", alias, user, contact, full_report_path, ) return file_path = f"refused-emails/{random_name}.eml" s3.upload_email_from_bytesio(file_path, BytesIO(orig_msg.as_bytes()), random_name) # <<< END Store the bounced email >>> mailbox_id = int(orig_msg[_MAILBOX_ID_HEADER]) mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != user.id: LOG.error( "Tampered message mailbox_id %s, %s, %s, %s %s", mailbox_id, user, alias, contact, full_report_path, ) return refused_email = RefusedEmail.create(path=file_path, full_report_path=full_report_path, user_id=user.id) db.session.flush() email_log.refused_email_id = refused_email.id email_log.bounced_mailbox_id = mailbox.id db.session.commit() LOG.d("Create refused email %s", refused_email) refused_email_url = (URL + f"/dashboard/refused_email?highlight_id=" + str(email_log.id)) # inform user if this is the first bounced email if nb_bounced == 1: LOG.d( "Inform user %s about bounced email sent by %s to alias %s", user, contact.website_email, address, ) send_email_with_rate_control( user, ALERT_BOUNCE_EMAIL, # use user mail here as only user is authenticated to see the refused email user.email, f"Email from {contact.website_email} to {address} cannot be delivered to your inbox", render( "transactional/bounced-email.txt", name=user.name, alias=alias, website_email=contact.website_email, disable_alias_link=disable_alias_link, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), render( "transactional/bounced-email.html", name=user.name, alias=alias, website_email=contact.website_email, disable_alias_link=disable_alias_link, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), # cannot include bounce email as it can contain spammy text # bounced_email=msg, ) # disable the alias the second time email is bounced elif nb_bounced >= 2: LOG.d( "Bounce happens again with alias %s from %s. Disable alias now ", address, contact.website_email, ) alias.enabled = False db.session.commit() send_email_with_rate_control( user, ALERT_BOUNCE_EMAIL, # use user mail here as only user is authenticated to see the refused email user.email, f"Alias {address} has been disabled due to second undelivered email from {contact.website_email}", render( "transactional/automatic-disable-alias.txt", name=user.name, alias=alias, website_email=contact.website_email, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), render( "transactional/automatic-disable-alias.html", name=user.name, alias=alias, website_email=contact.website_email, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), # cannot include bounce email as it can contain spammy text # bounced_email=msg, )
def handle_bounce(contact: Contact, alias: Alias, msg: Message, user: User): disable_alias_link = f"{URL}/dashboard/unsubscribe/{alias.id}" # Store the bounced email # generate a name for the email random_name = str(uuid.uuid4()) full_report_path = f"refused-emails/full-{random_name}.eml" s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()), random_name) file_path = None mailbox = None email_log: EmailLog = None orig_msg = get_orig_message_from_bounce(msg) if not orig_msg: # Some MTA does not return the original message in bounce message # nothing we can do here LOG.warning( "Cannot parse original message from bounce message %s %s %s %s", alias, user, contact, full_report_path, ) else: file_path = f"refused-emails/{random_name}.eml" s3.upload_email_from_bytesio(file_path, BytesIO(orig_msg.as_bytes()), random_name) try: mailbox_id = int(orig_msg[_MAILBOX_ID_HEADER]) except TypeError: LOG.exception( "cannot parse mailbox from original message header %s", orig_msg[_MAILBOX_ID_HEADER], ) else: mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != user.id: LOG.exception( "Tampered message mailbox_id %s, %s, %s, %s %s", mailbox_id, user, alias, contact, full_report_path, ) # cannot use this tampered mailbox, reset it mailbox = None # try to get the original email_log try: email_log_id = int(orig_msg[_EMAIL_LOG_ID_HEADER]) except TypeError: LOG.exception( "cannot parse email log from original message header %s", orig_msg[_EMAIL_LOG_ID_HEADER], ) else: email_log = EmailLog.get(email_log_id) refused_email = RefusedEmail.create(path=file_path, full_report_path=full_report_path, user_id=user.id) db.session.flush() LOG.d("Create refused email %s", refused_email) if not mailbox: LOG.debug("Try to get mailbox from bounce report") try: mailbox_id = int(get_header_from_bounce(msg, _MAILBOX_ID_HEADER)) except Exception: LOG.exception("cannot get mailbox-id from bounce report, %s", refused_email) else: mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != user.id: LOG.exception( "Tampered message mailbox_id %s, %s, %s, %s %s", mailbox_id, user, alias, contact, full_report_path, ) mailbox = None if not email_log: LOG.d("Try to get email log from bounce report") try: email_log_id = int( get_header_from_bounce(msg, _EMAIL_LOG_ID_HEADER)) except Exception: LOG.exception("cannot get email log id from bounce report, %s", refused_email) else: email_log = EmailLog.get(email_log_id) # use the default mailbox as the last option if not mailbox: LOG.warning("Use %s default mailbox %s", alias, refused_email) mailbox = alias.mailbox # create a new email log as the last option if not email_log: LOG.warning("cannot get the original email_log, create a new one") email_log: EmailLog = EmailLog.create(contact_id=contact.id, user_id=contact.user_id) email_log.bounced = True email_log.refused_email_id = refused_email.id email_log.bounced_mailbox_id = mailbox.id db.session.commit() refused_email_url = (URL + f"/dashboard/refused_email?highlight_id=" + str(email_log.id)) nb_bounced = EmailLog.filter_by(contact_id=contact.id, bounced=True).count() if nb_bounced >= 2 and alias.cannot_be_disabled: LOG.warning("%s cannot be disabled", alias) # inform user if this is the first bounced email if nb_bounced == 1 or (nb_bounced >= 2 and alias.cannot_be_disabled): LOG.d( "Inform user %s about bounced email sent by %s to alias %s", user, contact.website_email, alias, ) send_email_with_rate_control( user, ALERT_BOUNCE_EMAIL, user.email, f"Email from {contact.website_email} to {alias.email} cannot be delivered to your inbox", render( "transactional/bounced-email.txt", name=user.name, alias=alias, website_email=contact.website_email, disable_alias_link=disable_alias_link, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), render( "transactional/bounced-email.html", name=user.name, alias=alias, website_email=contact.website_email, disable_alias_link=disable_alias_link, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), ) # disable the alias the second time email is bounced elif nb_bounced >= 2: LOG.d( "Bounce happens again with alias %s from %s. Disable alias now ", alias, contact.website_email, ) alias.enabled = False db.session.commit() send_email_with_rate_control( user, ALERT_BOUNCE_EMAIL, user.email, f"Alias {alias.email} has been disabled due to second undelivered email from {contact.website_email}", render( "transactional/automatic-disable-alias.txt", name=user.name, alias=alias, website_email=contact.website_email, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), render( "transactional/automatic-disable-alias.html", name=user.name, alias=alias, website_email=contact.website_email, refused_email_url=refused_email_url, mailbox_email=mailbox.email, ), )