def send_one_off_notification(service_id, post_data):
    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id_and_service_id(
        template_id=post_data['template_id'],
        service_id=service_id
    )

    personalisation = post_data.get('personalisation', None)

    validate_template(template.id, personalisation, service, template.template_type)

    check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service)

    validate_and_format_recipient(
        send_to=post_data['to'],
        key_type=KEY_TYPE_NORMAL,
        service=service,
        notification_type=template.template_type,
        allow_whitelisted_recipients=False,
    )

    validate_created_by(service, post_data['created_by'])

    sender_id = post_data.get('sender_id', None)
    reply_to = get_reply_to_text(
        notification_type=template.template_type,
        sender_id=sender_id,
        service=service,
        template=template
    )
    notification = persist_notification(
        template_id=template.id,
        template_version=template.version,
        template_postage=template.postage,
        recipient=post_data['to'],
        service=service,
        personalisation=personalisation,
        notification_type=template.template_type,
        api_key_id=None,
        key_type=KEY_TYPE_NORMAL,
        created_by_id=post_data['created_by'],
        reply_to_text=reply_to,
        reference=create_one_off_reference(template.template_type),
    )

    queue_name = QueueNames.PRIORITY if template.process_type == PRIORITY else None

    if template.template_type == LETTER_TYPE and service.research_mode:
        _update_notification_status(
            notification,
            NOTIFICATION_DELIVERED,
        )
    else:
        send_notification_to_queue(
            notification=notification,
            research_mode=service.research_mode,
            queue=queue_name,
        )

    return {'id': str(notification.id)}
def process_sendgrid_response():
    data = json.loads(request.data)
    try:

        for obj in data:

            notification_status = get_sendgrid_responses(obj["event"])
            reference = obj['sg_message_id'].split(".")[0]

            notification = notifications_dao.dao_get_notification_by_reference(reference)

            notifications_dao._update_notification_status(notification=notification, status=notification_status)

            current_app.logger.info('SendGird callback return status of {} for notification: {}'.format(
                notification_status, notification.id
            ))

            statsd_client.incr('callback.sendgrid.{}'.format(notification_status))

            if notification.sent_at:
                statsd_client.timing_with_dates(
                    'callback.sendgrid.elapsed-time', datetime.utcnow(), notification.sent_at)

    except Exception as e:
        current_app.logger.exception('Error processing SendGrid results: {}'.format(type(e)))
        raise InvalidRequest(message="Error processing SendGrid results", status_code=400)
    else:
        return jsonify(result='success'), 200
def process_ses_results(response):
    try:
        ses_message = json.loads(response['Message'])
        notification_type = ses_message['notificationType']

        if notification_type == 'Bounce':
            notification_type = determine_notification_bounce_type(notification_type, ses_message)
        elif notification_type == 'Complaint':
            _check_and_queue_complaint_callback_task(*handle_complaint(ses_message))
            return True, False

        aws_response_dict = get_aws_responses(notification_type)

        notification_status = aws_response_dict['notification_status']
        reference = ses_message['mail']['messageId']

        try:
            notification = notifications_dao.dao_get_notification_by_reference(reference)
        except NoResultFound:
            message_time = iso8601.parse_date(ses_message['mail']['timestamp']).replace(tzinfo=None)
            if datetime.utcnow() - message_time < timedelta(minutes=5):
                return None, True

            current_app.logger.warning(
                "notification not found for reference: {} (update to {})".format(reference, notification_status)
            )
            return None, False

        if notification.status not in {NOTIFICATION_SENDING, NOTIFICATION_PENDING}:
            notifications_dao._duplicate_update_warning(notification, notification_status)
            return None, False

        notifications_dao._update_notification_status(notification=notification, status=notification_status)

        if not aws_response_dict['success']:
            current_app.logger.info(
                "SES delivery failed: notification id {} and reference {} has error found. Status {}".format(
                    notification.id, reference, aws_response_dict['message']
                )
            )
        else:
            current_app.logger.info('SES callback returned status of {} for notification: {}'.format(
                notification_status, notification.id
            ))

        statsd_client.incr('callback.ses.{}'.format(notification_status))

        if notification.sent_at:
            statsd_client.timing_with_dates('callback.ses.elapsed-time', datetime.utcnow(), notification.sent_at)

        _check_and_queue_callback_task(notification)

        return True, False
    except Exception as e:
        current_app.logger.exception('Error processing SES results: {}'.format(type(e)))
        return None, True
Beispiel #4
0
def send_one_off_notification(service_id, post_data):
    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id_and_service_id(template_id=post_data["template_id"], service_id=service_id)

    personalisation = post_data.get("personalisation", None)

    validate_template(template.id, personalisation, service, template.template_type)

    check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service)

    validate_and_format_recipient(
        send_to=post_data["to"],
        key_type=KEY_TYPE_NORMAL,
        service=service,
        notification_type=template.template_type,
        allow_safelisted_recipients=False,
    )

    validate_created_by(service, post_data["created_by"])

    sender_id = post_data.get("sender_id", None)
    reply_to = get_reply_to_text(
        notification_type=template.template_type,
        sender_id=sender_id,
        service=service,
        template=template,
    )
    notification = persist_notification(
        template_id=template.id,
        template_version=template.version,
        template_postage=template.postage,
        recipient=post_data["to"],
        service=service,
        personalisation=personalisation,
        notification_type=template.template_type,
        api_key_id=None,
        key_type=KEY_TYPE_NORMAL,
        created_by_id=post_data["created_by"],
        reply_to_text=reply_to,
        reference=create_one_off_reference(template.template_type),
    )

    if template.template_type == LETTER_TYPE and service.research_mode:
        _update_notification_status(
            notification,
            NOTIFICATION_DELIVERED,
        )
    else:
        send_notification_to_queue(
            notification=notification,
            research_mode=service.research_mode,
            queue=template.queue_to_use(),
        )

    return {"id": str(notification.id)}
Beispiel #5
0
def process_govdelivery_response():
    try:
        data = validate(request.form, govdelivery_webhook_schema)
        sid = data['sid']
        reference = data['message_url'].split("/")[-1]
        govdelivery_status = data['status']
        notify_status = govdelivery_status_map[govdelivery_status]

    except ValidationError as e:
        raise e
    except Exception as e:
        raise InvalidRequest(f'Error processing Govdelivery callback: {e}',
                             400)

    else:
        try:
            notification = notifications_dao.dao_get_notification_by_reference(
                reference)

        except (MultipleResultsFound, NoResultFound) as e:
            exception_type = type(e).__name__
            current_app.logger.exception(
                f'Govdelivery callback with sid {sid} for reference {reference} '
                f'did not find exactly one notification: {exception_type}')
            statsd_client.incr(
                f'callback.govdelivery.failure.{exception_type}')
        else:
            current_app.logger.info(
                f'Govdelivery callback for notification {notification.id} has status {govdelivery_status},'
                f' which maps to notification-api status {notify_status}')
            if data.get('error_message'):
                current_app.logger.info(
                    f"Govdelivery error_message for notification {notification.id}: "
                    f"{data['error_message']}")

            notifications_dao._update_notification_status(
                notification, notify_status)

            statsd_client.incr(f'callback.govdelivery.{notify_status}')

            if notification.sent_at:
                statsd_client.timing_with_dates(
                    'callback.govdelivery.elapsed-time', datetime.utcnow(),
                    notification.sent_at)

            if govdelivery_status == 'blacklisted':
                complaint = create_complaint(data, notification)
                publish_complaint(complaint, notification, notification.to)

    return jsonify(result='success'), 200
def process_govdelivery_response():
    try:
        data = request.form
        reference = data['message_url'].split("/")[-1]
        govdelivery_status = data['status']
        notify_status = map_govdelivery_status_to_notify_status(
            govdelivery_status)

    except Exception as e:
        raise InvalidRequest(
            'Error processing Govdelivery callback: {}'.format(e), 400)

    else:
        try:
            notification = notifications_dao.dao_get_notification_by_reference(
                reference)

        except (MultipleResultsFound, NoResultFound) as e:
            current_app.logger.exception(
                'Govdelivery callback for reference {} did not find exactly one notification: {}'
                .format(reference, type(e)))
            pass

        else:
            current_app.logger.info(
                'Govdelivery callback for notification {} has status "{}", which maps to notification-api status "{}"'
                .format(notification.id, govdelivery_status, notify_status))

            notifications_dao._update_notification_status(
                notification, notify_status)

            statsd_client.incr('callback.govdelivery.{}'.format(notify_status))

            if notification.sent_at:
                statsd_client.timing_with_dates(
                    'callback.govdelivery.elapsed-time', datetime.utcnow(),
                    notification.sent_at)

    return jsonify(result='success'), 200
Beispiel #7
0
def process_sns_delivery_status():
    callback = validate(request.get_json(), sns_delivery_status_schema)
    reference = callback['notification']['messageId']
    current_app.logger.debug(f"Full delivery response from AWS SNS for reference: {reference}\n{callback}")
    try:
        notification = dao_get_notification_by_reference(reference)
    except (NoResultFound, MultipleResultsFound) as e:
        current_app.logger.exception((
            f"AWS SNS delivery status callback for reference {reference} "
            f"did not find exactly one notification: {type(e)}"))
        return jsonify(result='error', message='Notification not found'), 404
    else:
        status = aws_sns_status_map.get(callback['status'])
        current_app.logger.info((
            f"AWS SNS delivery status callback for notification {notification.id} has status {callback['status']}"
            f", which maps to notification-api status {status}"))
        notification = _update_notification_status(notification, status)
        send_callback_metrics(notification)

        process_service_callback(notification)

    return jsonify({}), HTTPStatus.NO_CONTENT
def process_ses_results(self, response):
    try:
        ses_message = json.loads(response["Message"])
        notification_type = ses_message["notificationType"]

        if notification_type == "Complaint":
            _check_and_queue_complaint_callback_task(
                *handle_complaint(ses_message))
            return True

        aws_response_dict = get_aws_responses(ses_message)

        notification_status = aws_response_dict["notification_status"]
        reference = ses_message["mail"]["messageId"]

        try:
            notification = notifications_dao.dao_get_notification_by_reference(
                reference)
        except NoResultFound:
            message_time = iso8601.parse_date(
                ses_message["mail"]["timestamp"]).replace(tzinfo=None)
            if datetime.utcnow() - message_time < timedelta(minutes=5):
                self.retry(queue=QueueNames.RETRY)
            else:
                current_app.logger.warning(
                    "notification not found for reference: {} (update to {})".
                    format(reference, notification_status))
            return

        notifications_dao._update_notification_status(
            notification=notification,
            status=notification_status,
            provider_response=aws_response_dict["provider_response"],
        )

        if not aws_response_dict["success"]:
            current_app.logger.info(
                "SES delivery failed: notification id {} and reference {} has error found. Status {}"
                .format(notification.id, reference,
                        aws_response_dict["message"]))
        else:
            current_app.logger.info(
                "SES callback return status of {} for notification: {}".format(
                    notification_status, notification.id))

        statsd_client.incr("callback.ses.{}".format(notification_status))

        if notification.sent_at:
            statsd_client.timing_with_dates("callback.ses.elapsed-time",
                                            datetime.utcnow(),
                                            notification.sent_at)

        _check_and_queue_callback_task(notification)

        return True

    except Retry:
        raise

    except Exception as e:
        current_app.logger.exception("Error processing SES results: {}".format(
            type(e)))
        self.retry(queue=QueueNames.RETRY)
Beispiel #9
0
def send_one_off_notification(service_id, post_data):
    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id_and_service_id(
        template_id=post_data['template_id'],
        service_id=service_id
    )

    personalisation = post_data.get('personalisation', None)

    validate_template(template.id, personalisation, service, template.template_type)

    check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service)

    validate_and_format_recipient(
        send_to=post_data['to'],
        key_type=KEY_TYPE_NORMAL,
        service=service,
        notification_type=template.template_type,
        allow_guest_list_recipients=False,
    )
    postage = None
    client_reference = None
    if template.template_type == LETTER_TYPE:
        # Validate address and set postage to europe|rest-of-world if international letter,
        # otherwise persist_notification with use template postage
        postage = validate_address(service, personalisation)
        if not postage:
            postage = template.postage
        from app.utils import get_reference_from_personalisation
        client_reference = get_reference_from_personalisation(personalisation)

    validate_created_by(service, post_data['created_by'])

    sender_id = post_data.get('sender_id', None)
    reply_to = get_reply_to_text(
        notification_type=template.template_type,
        sender_id=sender_id,
        service=service,
        template=template
    )
    notification = persist_notification(
        template_id=template.id,
        template_version=template.version,
        recipient=post_data['to'],
        service=service,
        personalisation=personalisation,
        notification_type=template.template_type,
        api_key_id=None,
        key_type=KEY_TYPE_NORMAL,
        created_by_id=post_data['created_by'],
        reply_to_text=reply_to,
        reference=create_one_off_reference(template.template_type),
        postage=postage,
        client_reference=client_reference
    )

    queue_name = QueueNames.PRIORITY if template.process_type == PRIORITY else None

    if template.template_type == LETTER_TYPE and service.research_mode:
        _update_notification_status(
            notification,
            NOTIFICATION_DELIVERED,
        )
    else:
        send_notification_to_queue(
            notification=notification,
            research_mode=service.research_mode,
            queue=queue_name,
        )

    return {'id': str(notification.id)}
def process_sns_results(self, response):
    try:
        # Payload details: https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html
        sns_message = json.loads(response["Message"])
        reference = sns_message["notification"]["messageId"]
        sns_status = sns_message["status"]
        provider_response = sns_message["delivery"]["providerResponse"]

        try:
            notification_status = determine_status(sns_status, provider_response)
        except KeyError:
            current_app.logger.warning(f"unhandled provider response for reference {reference}, received '{provider_response}'")
            notification_status = NOTIFICATION_TECHNICAL_FAILURE
            provider_response = None

        try:
            notification = notifications_dao.dao_get_notification_by_reference(reference)
        except NoResultFound:
            message_time = iso8601.parse_date(sns_message["notification"]["timestamp"]).replace(tzinfo=None)
            if datetime.utcnow() - message_time < timedelta(minutes=5):
                self.retry(queue=QueueNames.RETRY)
            else:
                current_app.logger.warning(f"notification not found for reference: {reference} (update to {notification_status})")
            return

        if notification.sent_by != SNS_PROVIDER:
            current_app.logger.exception(f"SNS callback handled notification {notification.id} not sent by SNS")
            return

        if notification.status != NOTIFICATION_SENT:
            notifications_dao._duplicate_update_warning(notification, notification_status)
            return

        notifications_dao._update_notification_status(
            notification=notification,
            status=notification_status,
            provider_response=provider_response if notification_status == NOTIFICATION_TECHNICAL_FAILURE else None,
        )

        if notification_status != NOTIFICATION_DELIVERED:
            current_app.logger.info(
                (
                    f"SNS delivery failed: notification id {notification.id} and reference {reference} has error found. "
                    f"Provider response: {sns_message['delivery']['providerResponse']}"
                )
            )
        else:
            current_app.logger.info(f"SNS callback return status of {notification_status} for notification: {notification.id}")

        statsd_client.incr(f"callback.sns.{notification_status}")

        if notification.sent_at:
            statsd_client.timing_with_dates("callback.sns.elapsed-time", datetime.utcnow(), notification.sent_at)

        _check_and_queue_callback_task(notification)

        return True

    except Retry:
        raise

    except Exception as e:
        current_app.logger.exception(f"Error processing SNS results: {str(e)}")
        self.retry(queue=QueueNames.RETRY)
def process_sns_results(self, response):
    try:
        # Payload details: https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html
        sns_message = json.loads(response['Message'])
        reference = sns_message['notification']['messageId']
        status = sns_message['status']
        provider_response = sns_message['delivery']['providerResponse']

        # See all the possible provider responses
        # https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html#sms_stats_delivery_fail_reasons
        reasons = {
            'Blocked as spam by phone carrier': NOTIFICATION_TECHNICAL_FAILURE,
            'Destination is on a blocked list': NOTIFICATION_TECHNICAL_FAILURE,
            'Invalid phone number': NOTIFICATION_TECHNICAL_FAILURE,
            'Message body is invalid': NOTIFICATION_TECHNICAL_FAILURE,
            'Phone carrier has blocked this message': NOTIFICATION_TECHNICAL_FAILURE,
            'Phone carrier is currently unreachable/unavailable': NOTIFICATION_TEMPORARY_FAILURE,
            'Phone has blocked SMS': NOTIFICATION_TECHNICAL_FAILURE,
            'Phone is on a blocked list': NOTIFICATION_TECHNICAL_FAILURE,
            'Phone is currently unreachable/unavailable': NOTIFICATION_PERMANENT_FAILURE,
            'Phone number is opted out': NOTIFICATION_TECHNICAL_FAILURE,
            'This delivery would exceed max price': NOTIFICATION_TECHNICAL_FAILURE,
            'Unknown error attempting to reach phone': NOTIFICATION_TECHNICAL_FAILURE,
        }

        if status == "SUCCESS":
            notification_status = NOTIFICATION_DELIVERED
        else:
            if provider_response not in reasons:
                current_app.logger.warning(
                    f"unhandled provider response for reference {reference}, received '{provider_response}'"
                )
            notification_status = reasons.get(provider_response, NOTIFICATION_TECHNICAL_FAILURE)

        try:
            notification = notifications_dao.dao_get_notification_by_reference(reference)
        except NoResultFound:
            message_time = iso8601.parse_date(sns_message['notification']['timestamp']).replace(tzinfo=None)
            if datetime.utcnow() - message_time < timedelta(minutes=5):
                self.retry(queue=QueueNames.RETRY)
            else:
                current_app.logger.warning(
                    f"notification not found for reference: {reference} (update to {notification_status})"
                )
            return

        if notification.sent_by != SNS_PROVIDER:
            current_app.logger.exception(f'SNS callback handled notification {notification.id} not sent by SNS')
            return

        if notification.status != NOTIFICATION_SENT:
            notifications_dao._duplicate_update_warning(notification, notification_status)
            return

        notifications_dao._update_notification_status(
            notification=notification,
            status=notification_status
        )

        if notification_status != NOTIFICATION_DELIVERED:
            current_app.logger.info((
                f"SNS delivery failed: notification id {notification.id} and reference {reference} has error found. "
                f"Provider response: {sns_message['delivery']['providerResponse']}"
            ))
        else:
            current_app.logger.info(
                f'SNS callback return status of {notification_status} for notification: {notification.id}'
            )

        statsd_client.incr(f'callback.sns.{notification_status}')

        if notification.sent_at:
            statsd_client.timing_with_dates('callback.sns.elapsed-time', datetime.utcnow(), notification.sent_at)

        _check_and_queue_callback_task(notification)

        return True

    except Retry:
        raise

    except Exception as e:
        current_app.logger.exception(f'Error processing SNS results: {str(e)}')
        self.retry(queue=QueueNames.RETRY)