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
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)}
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
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)
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)