Example #1
0
def handle_email_sent_to_ourself(alias, mailbox, msg: Message, user):
    # store the refused email
    random_name = str(uuid.uuid4())
    full_report_path = f"refused-emails/cycle-{random_name}.eml"
    s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()),
                                 random_name)
    refused_email = RefusedEmail.create(path=None,
                                        full_report_path=full_report_path,
                                        user_id=alias.user_id)
    db.session.commit()
    LOG.d("Create refused email %s", refused_email)
    # link available for 6 days as it gets deleted in 7 days
    refused_email_url = refused_email.get_url(expires_in=518400)

    send_email_with_rate_control(
        user,
        ALERT_SEND_EMAIL_CYCLE,
        mailbox.email,
        f"Warning: email sent from {mailbox.email} to {alias.email} forms a cycle",
        render(
            "transactional/cycle-email.txt",
            name=user.name or "",
            alias=alias,
            mailbox=mailbox,
            refused_email_url=refused_email_url,
        ),
        render(
            "transactional/cycle-email.html",
            name=user.name or "",
            alias=alias,
            mailbox=mailbox,
            refused_email_url=refused_email_url,
        ),
    )
Example #2
0
def test_send_email_with_rate_control(flask_client):
    user = User.create(email="[email protected]",
                       password="******",
                       name="Test User",
                       activated=True)
    Session.commit()

    for _ in range(MAX_ALERT_24H):
        assert send_email_with_rate_control(user, "test alert type",
                                            "*****@*****.**", "subject",
                                            "plaintext")
    assert not send_email_with_rate_control(
        user, "test alert type", "*****@*****.**", "subject", "plaintext")
Example #3
0
def check_custom_domain():
    LOG.d("Check verified domain for DNS issues")

    for custom_domain in CustomDomain.query.filter_by(
        verified=True
    ):  # type: CustomDomain
        mx_domains = get_mx_domains(custom_domain.domain)

        if sorted(mx_domains) != sorted(EMAIL_SERVERS_WITH_PRIORITY):
            user = custom_domain.user
            LOG.warning(
                "The MX record is not correctly set for %s %s %s",
                custom_domain,
                user,
                mx_domains,
            )

            custom_domain.nb_failed_checks += 1

            # send alert if fail for 5 consecutive days
            if custom_domain.nb_failed_checks > 5:
                domain_dns_url = f"{URL}/dashboard/domains/{custom_domain.id}/dns"
                LOG.warning(
                    "Alert domain MX check fails %s about %s", user, custom_domain
                )
                send_email_with_rate_control(
                    user,
                    AlERT_WRONG_MX_RECORD_CUSTOM_DOMAIN,
                    user.email,
                    f"Please update {custom_domain.domain} DNS on SimpleLogin",
                    render(
                        "transactional/custom-domain-dns-issue.txt",
                        custom_domain=custom_domain,
                        domain_dns_url=domain_dns_url,
                    ),
                    render(
                        "transactional/custom-domain-dns-issue.html",
                        custom_domain=custom_domain,
                        domain_dns_url=domain_dns_url,
                    ),
                    max_nb_alert=1,
                    nb_day=30,
                )
                # reset checks
                custom_domain.nb_failed_checks = 0
        else:
            # reset checks
            custom_domain.nb_failed_checks = 0

        db.session.commit()
Example #4
0
def handle_unknown_mailbox(envelope, msg, reply_email: str, user: User,
                           alias: Alias):
    LOG.warning(
        f"Reply email can only be used by mailbox. "
        f"Actual mail_from: %s. msg from header: %s, reverse-alias %s, %s %s",
        envelope.mail_from,
        msg["From"],
        reply_email,
        alias,
        user,
    )

    send_email_with_rate_control(
        user,
        ALERT_REVERSE_ALIAS_UNKNOWN_MAILBOX,
        user.email,
        f"Reply from your alias {alias.email} only works from your mailbox",
        render(
            "transactional/reply-must-use-personal-email.txt",
            name=user.name,
            alias=alias,
            sender=envelope.mail_from,
        ),
        render(
            "transactional/reply-must-use-personal-email.html",
            name=user.name,
            alias=alias,
            sender=envelope.mail_from,
        ),
    )

    # Notify sender that they cannot send emails to this address
    send_email_with_rate_control(
        user,
        ALERT_REVERSE_ALIAS_UNKNOWN_MAILBOX,
        envelope.mail_from,
        f"Your email ({envelope.mail_from}) is not allowed to send emails to {reply_email}",
        render(
            "transactional/send-from-alias-from-unknown-sender.txt",
            sender=envelope.mail_from,
            reply_email=reply_email,
        ),
        render(
            "transactional/send-from-alias-from-unknown-sender.html",
            sender=envelope.mail_from,
            reply_email=reply_email,
        ),
    )
Example #5
0
def check_custom_domain():
    LOG.d("Check verified domain for DNS issues")

    for custom_domain in CustomDomain.query.filter(
            CustomDomain.verified == True):
        mx_domains = get_mx_domains(custom_domain.domain)

        if sorted(mx_domains) != sorted(EMAIL_SERVERS_WITH_PRIORITY):
            user = custom_domain.user
            LOG.warning(
                "The MX record is not correctly set for %s %s %s",
                custom_domain,
                user,
                mx_domains,
            )

            domain_dns_url = f"{URL}/dashboard/domains/{custom_domain.id}/dns"

            send_email_with_rate_control(
                user,
                AlERT_WRONG_MX_RECORD_CUSTOM_DOMAIN,
                user.email,
                f"Please update {custom_domain.domain} DNS on SimpleLogin",
                render(
                    "transactional/custom-domain-dns-issue.txt",
                    custom_domain=custom_domain,
                    name=user.name or "",
                    domain_dns_url=domain_dns_url,
                ),
                render(
                    "transactional/custom-domain-dns-issue.html",
                    custom_domain=custom_domain,
                    name=user.name or "",
                    domain_dns_url=domain_dns_url,
                ),
                max_nb_alert=1,
                nb_day=30,
            )
Example #6
0
def check_single_custom_domain(custom_domain):
    mx_domains = get_mx_domains(custom_domain.domain)
    if not is_mx_equivalent(mx_domains, EMAIL_SERVERS_WITH_PRIORITY):
        user = custom_domain.user
        LOG.w(
            "The MX record is not correctly set for %s %s %s",
            custom_domain,
            user,
            mx_domains,
        )

        custom_domain.nb_failed_checks += 1

        # send alert if fail for 5 consecutive days
        if custom_domain.nb_failed_checks > 5:
            domain_dns_url = f"{URL}/dashboard/domains/{custom_domain.id}/dns"
            LOG.w("Alert domain MX check fails %s about %s", user,
                  custom_domain)
            send_email_with_rate_control(
                user,
                AlERT_WRONG_MX_RECORD_CUSTOM_DOMAIN,
                user.email,
                f"Please update {custom_domain.domain} DNS on SimpleLogin",
                render(
                    "transactional/custom-domain-dns-issue.txt.jinja2",
                    custom_domain=custom_domain,
                    domain_dns_url=domain_dns_url,
                ),
                max_nb_alert=1,
                nb_day=30,
                retries=3,
            )
            # reset checks
            custom_domain.nb_failed_checks = 0
    else:
        # reset checks
        custom_domain.nb_failed_checks = 0
    Session.commit()
Example #7
0
def handle_spam(
    contact: Contact,
    alias: Alias,
    msg: Message,
    user: User,
    mailbox_email: str,
    email_log: EmailLog,
):
    # Store the report & original email
    orig_msg = get_orig_message_from_spamassassin_report(msg)
    # generate a name for the email
    random_name = str(uuid.uuid4())

    full_report_path = f"spams/full-{random_name}.eml"
    s3.upload_email_from_bytesio(full_report_path, BytesIO(msg.as_bytes()),
                                 random_name)

    file_path = None
    if orig_msg:
        file_path = f"spams/{random_name}.eml"
        s3.upload_email_from_bytesio(file_path, BytesIO(orig_msg.as_bytes()),
                                     random_name)

    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
    db.session.commit()

    LOG.d("Create spam email %s", refused_email)

    refused_email_url = (URL + f"/dashboard/refused_email?highlight_id=" +
                         str(email_log.id))
    disable_alias_link = f"{URL}/dashboard/unsubscribe/{alias.id}"

    # inform user
    LOG.d(
        "Inform user %s about spam email sent by %s to alias %s",
        user,
        contact.website_email,
        alias.email,
    )
    send_email_with_rate_control(
        user,
        ALERT_SPAM_EMAIL,
        mailbox_email,
        f"Email from {contact.website_email} to {alias.email} is detected as spam",
        render(
            "transactional/spam-email.txt",
            name=user.name,
            alias=alias,
            website_email=contact.website_email,
            disable_alias_link=disable_alias_link,
            refused_email_url=refused_email_url,
        ),
        render(
            "transactional/spam-email.html",
            name=user.name,
            alias=alias,
            website_email=contact.website_email,
            disable_alias_link=disable_alias_link,
            refused_email_url=refused_email_url,
        ),
    )
Example #8
0
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,
        )
Example #9
0
def spf_pass(
    ip: str,
    envelope,
    mailbox: Mailbox,
    user: User,
    alias: Alias,
    contact_email: str,
    msg: Message,
) -> bool:
    if ip:
        LOG.d("Enforce SPF")
        try:
            r = spf.check2(i=ip, s=envelope.mail_from.lower(), h=None)
        except Exception:
            LOG.error("SPF error, mailbox %s, ip %s", mailbox.email, ip)
        else:
            # TODO: Handle temperr case (e.g. dns timeout)
            # only an absolute pass, or no SPF policy at all is 'valid'
            if r[0] not in ["pass", "none"]:
                LOG.error(
                    "SPF fail for mailbox %s, reason %s, failed IP %s",
                    mailbox.email,
                    r[0],
                    ip,
                )
                send_email_with_rate_control(
                    user,
                    ALERT_SPF,
                    mailbox.email,
                    f"SimpleLogin Alert: attempt to send emails from your alias {alias.email} from unknown IP Address",
                    render(
                        "transactional/spf-fail.txt",
                        name=user.name,
                        alias=alias.email,
                        ip=ip,
                        mailbox_url=URL +
                        f"/dashboard/mailbox/{mailbox.id}#spf",
                        to_email=contact_email,
                        subject=msg["Subject"],
                        time=arrow.now(),
                    ),
                    render(
                        "transactional/spf-fail.html",
                        name=user.name,
                        alias=alias.email,
                        ip=ip,
                        mailbox_url=URL +
                        f"/dashboard/mailbox/{mailbox.id}#spf",
                        to_email=contact_email,
                        subject=msg["Subject"],
                        time=arrow.now(),
                    ),
                )
                return False

    else:
        LOG.warning(
            "Could not find %s header %s -> %s",
            _IP_HEADER,
            mailbox.email,
            contact_email,
        )

    return True
Example #10
0
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,
            ),
        )
Example #11
0
async def forward_email_to_mailbox(
    alias,
    msg: Message,
    email_log: EmailLog,
    contact: Contact,
    envelope,
    smtp: SMTP,
    mailbox,
    user,
) -> (bool, str):
    LOG.d("Forward %s -> %s -> %s", contact, alias, mailbox)

    # sanity check: make sure mailbox is not actually an alias
    if get_email_domain_part(alias.email) == get_email_domain_part(
            mailbox.email):
        LOG.warning(
            "Mailbox has the same domain as alias. %s -> %s -> %s",
            contact,
            alias,
            mailbox,
        )
        mailbox_url = f"{URL}/dashboard/mailbox/{mailbox.id}/"
        send_email_with_rate_control(
            user,
            ALERT_MAILBOX_IS_ALIAS,
            user.email,
            f"Your SimpleLogin mailbox {mailbox.email} cannot be an email alias",
            render(
                "transactional/mailbox-invalid.txt",
                name=user.name or "",
                mailbox=mailbox,
                mailbox_url=mailbox_url,
            ),
            render(
                "transactional/mailbox-invalid.html",
                name=user.name or "",
                mailbox=mailbox,
                mailbox_url=mailbox_url,
            ),
            max_nb_alert=1,
        )

        # retry later
        # so when user fixes the mailbox, the email can be delivered
        return False, "421 SL E14"

    # Spam check
    spam_status = ""
    is_spam = False

    if SPAMASSASSIN_HOST:
        start = time.time()
        spam_score = await get_spam_score(msg)
        LOG.d(
            "%s -> %s - spam score %s in %s seconds",
            contact,
            alias,
            spam_score,
            time.time() - start,
        )
        email_log.spam_score = spam_score
        db.session.commit()

        if (user.max_spam_score and spam_score > user.max_spam_score) or (
                not user.max_spam_score and spam_score > MAX_SPAM_SCORE):
            is_spam = True
            spam_status = "Spam detected by SpamAssassin server"
    else:
        is_spam, spam_status = get_spam_info(msg,
                                             max_score=user.max_spam_score)

    if is_spam:
        LOG.warning("Email detected as spam. Alias: %s, from: %s", alias,
                    contact)
        email_log.is_spam = True
        email_log.spam_status = spam_status
        db.session.commit()

        handle_spam(contact, alias, msg, user, mailbox, email_log)
        return False, "550 SL E1 Email detected as spam"

    # create PGP email if needed
    if mailbox.pgp_finger_print and user.is_premium(
    ) and not alias.disable_pgp:
        LOG.d("Encrypt message using mailbox %s", mailbox)
        try:
            msg = prepare_pgp_message(msg, mailbox.pgp_finger_print)
        except PGPException:
            LOG.exception("Cannot encrypt message %s -> %s. %s %s", contact,
                          alias, mailbox, user)
            # so the client can retry later
            return False, "421 SL E12 Retry later"

    # add custom header
    add_or_replace_header(msg, _DIRECTION, "Forward")

    # remove reply-to & sender header if present
    delete_header(msg, "Reply-To")
    delete_header(msg, "Sender")

    delete_header(msg, _IP_HEADER)
    add_or_replace_header(msg, _MAILBOX_ID_HEADER, str(mailbox.id))
    add_or_replace_header(msg, _EMAIL_LOG_ID_HEADER, str(email_log.id))
    add_or_replace_header(msg, _MESSAGE_ID,
                          make_msgid(str(email_log.id), EMAIL_DOMAIN))

    # change the from header so the sender comes from @SL
    # so it can pass DMARC check
    # replace the email part in from: header
    contact_from_header = msg["From"]
    new_from_header = contact.new_addr()
    add_or_replace_header(msg, "From", new_from_header)
    LOG.d("new_from_header:%s, old header %s", new_from_header,
          contact_from_header)

    # replace CC & To emails by reply-email for all emails that are not alias
    replace_header_when_forward(msg, alias, "Cc")
    replace_header_when_forward(msg, alias, "To")

    # append alias into the TO header if it's not present in To or CC
    if should_append_alias(msg, alias.email):
        LOG.d("append alias %s  to TO header %s", alias, msg["To"])
        if msg["To"]:
            to_header = msg["To"] + "," + alias.email
        else:
            to_header = alias.email

        add_or_replace_header(msg, "To", to_header.strip())

    # add List-Unsubscribe header
    if UNSUBSCRIBER:
        unsubscribe_link = f"mailto:{UNSUBSCRIBER}?subject={alias.id}="
        add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>")
    else:
        unsubscribe_link = f"{URL}/dashboard/unsubscribe/{alias.id}"
        add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>")
        add_or_replace_header(msg, "List-Unsubscribe-Post",
                              "List-Unsubscribe=One-Click")

    add_dkim_signature(msg, EMAIL_DOMAIN)

    LOG.d(
        "Forward mail from %s to %s, mail_options %s, rcpt_options %s ",
        contact.website_email,
        mailbox.email,
        envelope.mail_options,
        envelope.rcpt_options,
    )

    # smtp.send_message has UnicodeEncodeErroremail issue
    # encode message raw directly instead
    try:
        smtp.sendmail(
            contact.reply_email,
            mailbox.email,
            msg.as_bytes(),
            envelope.mail_options,
            envelope.rcpt_options,
        )
    except SMTPRecipientsRefused:
        # that means the mailbox is maybe invalid
        LOG.warning(
            "SMTPRecipientsRefused forward phase %s -> %s -> %s",
            contact,
            alias,
            mailbox,
        )
        # return 421 so Postfix can retry later
        return False, "421 SL E17 Retry later"
    else:
        db.session.commit()
        return True, "250 Message accepted for delivery"