Exemplo n.º 1
0
    def test_hard_bounce_emails(self):
        recipients = [
            '*****@*****.**',
            self.SUPPRESSED_BOUNCE,
            self.UNDETERMINED_BOUNCE,
            self.GENERAL_BOUNCE,
            self.GENERAL_AT_TRESH,
            self.GENERAL_ABOVE_THRESH,
            self.TRANSIENT_AT_THRESH,
            self.TRANSIENT_ABOVE_THRESH,
            self.EXPIRED_TRANSIENT_AT_THRESH,
            self.COMPLAINT,
            self.LEGACY_BOUNCE,
        ]

        bounced_emails = BouncedEmail.get_hard_bounced_emails(recipients)
        print(bounced_emails)
        self.assertEqual(
            bounced_emails,
            {
                self.SUPPRESSED_BOUNCE,
                self.UNDETERMINED_BOUNCE,
                self.GENERAL_ABOVE_THRESH,
                self.TRANSIENT_ABOVE_THRESH,
                self.COMPLAINT,
                self.LEGACY_BOUNCE,
            }
        )
    def test_hard_bounce_emails(self):
        recipients = [
            '*****@*****.**',
            self.SUPPRESSED_BOUNCE,
            self.UNDETERMINED_BOUNCE,
            self.GENERAL_BOUNCE,
            self.GENERAL_AT_TRESH,
            self.GENERAL_ABOVE_THRESH,
            self.TRANSIENT_AT_THRESH,
            self.TRANSIENT_ABOVE_THRESH,
            self.EXPIRED_TRANSIENT_AT_THRESH,
            self.COMPLAINT,
            self.LEGACY_BOUNCE,
            self.BLOCKED_BY_TOGGLE,
            self.BAD_FORMAT_MISSING_TLD,
            self.BAD_FORMAT_MISSING_AT,
        ]

        bounced_emails = BouncedEmail.get_hard_bounced_emails(recipients)
        self.assertEqual(
            bounced_emails, {
                self.SUPPRESSED_BOUNCE,
                self.UNDETERMINED_BOUNCE,
                self.GENERAL_ABOVE_THRESH,
                self.TRANSIENT_ABOVE_THRESH,
                self.COMPLAINT,
                self.LEGACY_BOUNCE,
                self.BLOCKED_BY_TOGGLE,
                self.BAD_FORMAT_MISSING_TLD,
                self.BAD_FORMAT_MISSING_AT,
            })
Exemplo n.º 3
0
def get_valid_recipients(recipients, domain=None):
    """
    This filters out any emails that have reported hard bounces or complaints to
    Amazon SES
    :param recipients: list of recipient emails
    :return: list of recipient emails not marked as bounced
    """
    from corehq.toggles import BLOCKED_DOMAIN_EMAIL_SENDERS
    if domain and BLOCKED_DOMAIN_EMAIL_SENDERS.enabled(domain):
        # don't sent email if domain is blocked
        metrics_gauge('commcare.bounced_email', len(recipients), tags={
            'email_domain': domain,
        }, multiprocess_mode=MPM_LIVESUM)
        return []

    from corehq.util.models import BouncedEmail
    bounced_emails = BouncedEmail.get_hard_bounced_emails(recipients)
    for bounced_email in bounced_emails:
        try:
            email_domain = bounced_email.split('@')[1]
        except IndexError:
            email_domain = bounced_email
        metrics_gauge('commcare.bounced_email', 1, tags={
            'email_domain': email_domain,
        }, multiprocess_mode=MPM_LIVESUM)
    return [recipient for recipient in recipients if recipient not in bounced_emails]
Exemplo n.º 4
0
def get_valid_recipients(recipients):
    """
    This filters out any emails that have reported hard bounces or complaints to
    Amazon SES
    :param recipients: list of recipient emails
    :return: list of recipient emails not marked as bounced
    """
    from corehq.util.models import BouncedEmail
    bounced_emails = BouncedEmail.get_hard_bounced_emails(recipients)
    return [recipient for recipient in recipients if recipient not in bounced_emails]
Exemplo n.º 5
0
    def mark_email_as_bounced(self, email_string):
        is_actively_blocked = (len(
            BouncedEmail.get_hard_bounced_emails([email_string])) > 0)
        if is_actively_blocked:
            self.stdout.write(
                f"{email_string} is already blocked. "
                f"Use check_bounced_email --show-details for more information."
            )
            return

        bounced_email = BouncedEmail.objects.create(email=email_string)
        PermanentBounceMeta.objects.create(
            bounced_email=bounced_email,
            timestamp=datetime.datetime.utcnow(),
            sub_type=BounceSubType.SUPPRESSED,
            reason="Manual suppression from management command.")
        self.stdout.write(f"Successfully marked {email_string} as bounced.")
    def test_hard_bounce_emails(self):
        recipients = [
            '*****@*****.**',
            self.SUPPRESSED_BOUNCE,
            self.UNDETERMINED_BOUNCE,
            self.GENERAL_BOUNCE,
            self.GENERAL_AT_TRESH,
            self.GENERAL_ABOVE_THRESH,
            self.COMPLAINT,
            self.NO_META_BOUNCE,
        ]

        bounced_emails = BouncedEmail.get_hard_bounced_emails(recipients)
        self.assertEqual(
            bounced_emails, {
                self.SUPPRESSED_BOUNCE,
                self.UNDETERMINED_BOUNCE,
                self.GENERAL_ABOVE_THRESH,
                self.COMPLAINT,
                self.NO_META_BOUNCE,
            })
Exemplo n.º 7
0
def get_valid_recipients(recipients):
    """
    This filters out any emails that have reported hard bounces or complaints to
    Amazon SES
    :param recipients: list of recipient emails
    :return: list of recipient emails not marked as bounced
    """
    from corehq.util.models import BouncedEmail
    bounced_emails = BouncedEmail.get_hard_bounced_emails(recipients)
    for bounced_email in bounced_emails:
        try:
            email_domain = bounced_email.split('@')[1]
        except IndexError:
            email_domain = bounced_email
        metrics_gauge('commcare.bounced_email',
                      1,
                      tags={
                          'email_domain': email_domain,
                      })
    return [
        recipient for recipient in recipients
        if recipient not in bounced_emails
    ]
Exemplo n.º 8
0
    def check_bounced_email(self, email_string, show_details):
        is_actively_blocked = (len(
            BouncedEmail.get_hard_bounced_emails([email_string])) > 0)
        if not is_actively_blocked:
            self.stdout.write(f'{email_string} is NOT blocked. ' f'All clear!')
            return
        else:
            self.stdout.write(
                f'{email_string} is blocked! \n'
                f'Please note that we block emails due to the following reasons:\n'
                f'\t- a permanent suppressed bounce is present\n'
                f'\t- more than {BOUNCE_EVENT_THRESHOLD} general '
                f'and/or transient bounces have been received\n'
                f'\nnote that transient bounce information expires '
                f'after {HOURS_UNTIL_TRANSIENT_BOUNCES_EXPIRE} hours\n\n')

        bounce_query = BouncedEmail.objects.filter(email=email_string)
        if bounce_query.exists():
            bounced_email = bounce_query.first()
            permanent_bounces_query = PermanentBounceMeta.objects.filter(
                bounced_email=bounced_email).order_by('-created')
            complaints_query = ComplaintBounceMeta.objects.filter(
                bounced_email=bounced_email).order_by('-created')

            total_permanent = permanent_bounces_query.count()
            total_complaints = complaints_query.count()

            latest_permanent = (
                permanent_bounces_query.first().created.isoformat()
                if permanent_bounces_query.first() else "\t\t\t")
            latest_complaint = (complaints_query.first().created.isoformat()
                                if complaints_query.first() else "\t\t\t")
        else:
            bounced_email = None
            total_permanent = 0
            total_complaints = 0
            permanent_bounces_query = None
            complaints_query = None
            latest_permanent = None
            latest_complaint = None

        transient_query = TransientBounceEmail.objects.filter(
            email=email_string).order_by('-created')
        total_transient = transient_query.count()

        latest_transient = (transient_query.first().created.isoformat()
                            if transient_query.first() else "\t\t\t")

        self.stdout.write(f'\nEmail\t\t'
                          f'\tNumber Permanent'
                          f'\tLast Recorded on'
                          f'\t\tNumber Complaints'
                          f'\tLast Recorded on'
                          f'\t\tNumber Transient'
                          f'\tLast Recorded on')

        self.stdout.write(f'{email_string}')

        self.stdout.write(f'\t\t\t'
                          f'\t{total_permanent}\t\t'
                          f'\t{latest_permanent}'
                          f'\t{total_complaints}\t\t'
                          f'\t{latest_complaint}'
                          f'\t{total_transient}\t\t'
                          f'\t{latest_transient}')

        if not show_details:
            return

        self.stdout.write('\n\tDETAILS:\n\n')

        if not is_actively_blocked and bounced_email:
            self.stdout.write(
                '\n\tThis email has a bounce record, but it is NOT being '
                'prevented getting HQ emails.')
            self.stdout.write(
                f'\tThe record was created on {bounced_email.created.isoformat()} '
                f'due to a non-SNS bounce that came to [email protected]\n'
                f'\tIt is not possible to determine if this bounce was TRANSIENT '
                f'or PERMANENT until an SNS record comes in.\n\n')

        if transient_query.exists():
            self.stdout.write('\t\nTransient Bounce Records:\n\n')
            self.stdout.write('\tCreated\t\t\t'
                              '\tSNS Timestamp\t\t'
                              '\tHeaders')
            self.stdout.write('\t' + '_' * 200)
            for record in transient_query.all():
                self.stdout.write(f'\t{record.created.isoformat()}'
                                  f'\t{record.timestamp}')
                for key, val in record.headers.items():
                    self.stdout.write(f'\t' * 10 + f'{key}:\t{val}')
            self.stdout.write('\n\nt')

        if permanent_bounces_query and permanent_bounces_query.exists():
            self.stdout.write('\tPermanent Bounce Records:\n\n')
            self.stdout.write('\tSub-Type'
                              '\t\tSNS Timestamp'
                              '\t\t\tCreated on HQ'
                              '\t\t\tReason'
                              '\t\t\tHeaders')
            self.stdout.write('\t' + '_' * 200)
            for record in permanent_bounces_query.all():
                self.stdout.write(f'\t{record.sub_type}'
                                  f'\t\t{record.timestamp}'
                                  f'\t{record.created}'
                                  f'\t{record.reason}')
                for key, val in record.headers.items():
                    self.stdout.write(f'\t' * 15 + f'{key}:\t{val}')
                self.stdout.write(f'\t' * 15 +
                                  f'destination:\t{record.destination}')
            self.stdout.write('\n\n')

        if complaints_query and complaints_query.exists():
            self.stdout.write('\tComplaint Records:')
            self.stdout.write('\tSNS Timestamp'
                              '\t\t\tCreated on HQ'
                              '\t\t\tFeedback Type'
                              '\t\tSub-Type'
                              '\tHeaders')
            self.stdout.write('\t' + '_' * 200)
            for record in complaints_query.all():
                self.stdout.write(f'\t{record.timestamp}'
                                  f'\t{record.created}')
                self.stdout.write(f'\t' * 8 + f'\t{record.feedback_type}')
                self.stdout.write(f'\t' * 11 + f'\t{record.sub_type}')
                self.stdout.write(f'\t' * 14 +
                                  f'destination: {record.destination}')
                for key, val in record.headers.items():
                    self.stdout.write(f'\t' * 14 + f'{key}:\t{val}')
            self.stdout.write('\n\n')
    def check_bounced_email(self, email_string, show_details):
        bounce_query = BouncedEmail.objects.filter(email=email_string)

        if not bounce_query.exists():
            self.stdout.write(f'{email_string} is NOT bouncing. '
                              f'All clear!')
            return

        bounced_email = BouncedEmail.objects.filter(email=email_string).first()

        is_actively_blocked = (len(
            BouncedEmail.get_hard_bounced_emails([bounced_email.email])) > 0)
        block_status = "YES" if is_actively_blocked else "NO"

        permanent_bounces_query = PermanentBounceMeta.objects.filter(
            bounced_email=bounced_email).order_by('-created')
        complaints_query = ComplaintBounceMeta.objects.filter(
            bounced_email=bounced_email).order_by('-created')
        transient_query = TransientBounceEmail.objects.filter(
            email=bounced_email.email).order_by('-created')

        total_permanent = permanent_bounces_query.count()
        total_complaints = complaints_query.count()
        total_transient = transient_query.count()

        latest_permanent = (
            permanent_bounces_query.first().created.isoformat()
            if permanent_bounces_query.first() else "\t\t\t")
        latest_complaint = (complaints_query.first().created.isoformat()
                            if complaints_query.first() else "\t\t\t")
        latest_transient = (transient_query.first().created.isoformat()
                            if transient_query.first() else "\t\t\t")

        self.stdout.write(f'{bounced_email.email}')

        self.stdout.write(f'\t\t\t'
                          f'\t{block_status}\t\t'
                          f'\t{total_permanent}\t\t'
                          f'\t{latest_permanent}'
                          f'\t{total_complaints}\t\t'
                          f'\t{latest_complaint}'
                          f'\t{total_transient}\t\t'
                          f'\t{latest_transient}')

        if not show_details:
            return

        self.stdout.write('\n\tDETAILS:\n\n')

        if not is_actively_blocked:
            self.stdout.write(
                '\n\tThis email has a bounce record, but it is NOT being '
                'prevented getting HQ emails.')
            self.stdout.write(
                f'\tThe record was created on {bounced_email.created.isoformat()} '
                f'due to a non-SNS bounce that came to [email protected]\n'
                f'\tIt is not possible to determine if this bounce was TRANSIENT '
                f'or PERMANENT until an SNS record comes in.\n\n')

        if transient_query.exists():
            self.stdout.write('\t\nTransient Bounce Records:\n\n')
            self.stdout.write('\tCreated\t\t\t'
                              '\tSNS Timestamp\t\t'
                              '\tHeaders')
            self.stdout.write('\t' + '_' * 200)
            for record in transient_query.all():
                self.stdout.write(f'\t{record.created.isoformat()}'
                                  f'\t{record.timestamp}')
                for key, val in record.headers.items():
                    self.stdout.write(f'\t' * 10 + f'{key}:\t{val}')
            self.stdout.write('\n\nt')

        if permanent_bounces_query.exists():
            self.stdout.write('\tPermanent Bounce Records:\n\n')
            self.stdout.write('\tSub-Type'
                              '\t\tSNS Timestamp'
                              '\t\t\tCreated on HQ'
                              '\t\t\tReason'
                              '\t\t\tHeaders')
            self.stdout.write('\t' + '_' * 200)
            for record in permanent_bounces_query.all():
                self.stdout.write(f'\t{record.sub_type}'
                                  f'\t\t{record.timestamp}'
                                  f'\t{record.created}'
                                  f'\t{record.reason}')
                for key, val in record.headers.items():
                    self.stdout.write(f'\t' * 15 + f'{key}:\t{val}')
                self.stdout.write(f'\t' * 15 +
                                  f'destination:\t{record.destination}')
            self.stdout.write('\n\n')

        if complaints_query.exists():
            self.stdout.write('\tComplaint Records:')
            self.stdout.write('\tSNS Timestamp'
                              '\t\t\tCreated on HQ'
                              '\t\t\tFeedback Type'
                              '\t\tSub-Type'
                              '\tHeaders')
            self.stdout.write('\t' + '_' * 200)
            for record in complaints_query.all():
                self.stdout.write(f'\t{record.timestamp}'
                                  f'\t{record.created}')
                self.stdout.write(f'\t' * 8 + f'\t{record.feedback_type}')
                self.stdout.write(f'\t' * 11 + f'\t{record.sub_type}')
                self.stdout.write(f'\t' * 14 +
                                  f'destination: {record.destination}')
                for key, val in record.headers.items():
                    self.stdout.write(f'\t' * 14 + f'{key}:\t{val}')
            self.stdout.write('\n\n')