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 test_should_return_correct_details_for_delivery(): response_dict = get_aws_responses('Delivery') assert response_dict['message'] == 'Delivered' assert response_dict['notification_status'] == 'delivered' assert response_dict['notification_statistics_status'] == 'delivered' assert response_dict['success']
def test_should_be_none_if_unrecognised_status_code(): with pytest.raises(KeyError) as e: get_aws_responses('99') assert '99' in str(e.value)
def test_should_return_correct_details_for_soft_bounced(): response_dict = get_aws_responses('Temporary') assert response_dict['message'] == 'Soft bounced' assert response_dict['notification_status'] == 'temporary-failure' assert response_dict['notification_statistics_status'] == 'failure' assert not response_dict['success']
def test_should_return_correct_details_for_hard_bounced(): response_dict = get_aws_responses('Permanent') assert response_dict['message'] == 'Hard bounced' assert response_dict['notification_status'] == 'permanent-failure' assert response_dict['notification_statistics_status'] == 'failure' assert not response_dict['success']
def process_ses_results(self, response): try: ses_message = json.loads(response['Message']) notification_type = ses_message['notificationType'] bounce_message = None if notification_type == 'Bounce': notification_type, bounce_message = determine_notification_bounce_type(notification_type, ses_message) elif notification_type == 'Complaint': _check_and_queue_complaint_callback_task(*handle_complaint(ses_message)) return True 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_or_history_by_reference(reference=reference) except NoResultFound: message_time = iso8601.parse_date(ses_message['mail']['timestamp']).replace(tzinfo=None) if datetime.utcnow() - message_time < timedelta(minutes=5): current_app.logger.info( f"notification not found for reference: {reference} (update to {notification_status}). " f"Callback may have arrived before notification was persisted to the DB. Adding task to retry queue" ) self.retry(queue=QueueNames.RETRY) else: current_app.logger.warning( f"notification not found for reference: {reference} (update to {notification_status})" ) return if bounce_message: current_app.logger.info(f"SES bounce for notification ID {notification.id}: {bounce_message}") if notification.status not in [NOTIFICATION_SENDING, NOTIFICATION_PENDING]: notifications_dao._duplicate_update_warning( notification=notification, status=notification_status ) return else: notifications_dao.dao_update_notifications_by_reference( references=[reference], update_dict={'status': notification_status} ) 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 process_ses_smtp_results(self, response): try: ses_message = json.loads(response['Message']) notification_type = ses_message['notificationType'] headers = ses_message['mail']['commonHeaders'] source = ses_message['mail']['source'] recipients = ses_message['mail']['destination'] if notification_type == 'Bounce': notification_type = determine_notification_bounce_type( notification_type, ses_message) aws_response_dict = get_aws_responses(notification_type) notification_status = aws_response_dict['notification_status'] try: # Get service based on SMTP name service = services_dao.dao_services_by_partial_smtp_name( source.split("@")[-1]) # Create a sent notification based on details from the payload template = templates_dao.dao_get_template_by_id( current_app.config['SMTP_TEMPLATE_ID']) for recipient in recipients: message = "".join(( 'A message was sent from: \n', # noqa: E126 source, '\n\n to: \n', recipient, '\n\n on: \n', headers["date"], '\n\n with the subject: \n', headers["subject"])) notification = process_notifications.persist_notification( template_id=template.id, template_version=template.version, recipient=recipient, service=service, personalisation={ 'subject': headers["subject"], 'message': message }, notification_type=EMAIL_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=recipient, created_at=headers["date"], status=notification_status, reference=ses_message['mail']['messageId']) if notification_type == 'Complaint': _check_and_queue_complaint_callback_task( *handle_smtp_complaint(ses_message)) else: _check_and_queue_callback_task(notification) except NoResultFound: reference = ses_message['mail']['messageId'] current_app.logger.warning( "SMTP service not found for reference: {} (update to {})". format(reference, notification_status)) return statsd_client.incr('callback.ses-smtp.{}'.format(notification_status)) return True except Retry: raise except Exception as e: current_app.logger.exception( 'Error processing SES SMTP results: {}'.format(type(e))) self.retry(queue=QueueNames.RETRY)
def process_ses_response(ses_request): client_name = 'SES' try: errors = validate_callback_data(data=ses_request, fields=['Message'], client_name=client_name) if errors: return errors ses_message = json.loads(ses_request['Message']) errors = validate_callback_data(data=ses_message, fields=['notificationType'], client_name=client_name) if errors: return errors notification_type = ses_message['notificationType'] if notification_type == 'Bounce': notification_type = determine_notification_bounce_type( notification_type, ses_message) elif notification_type == 'Complaint': complaint, notification, recipient = handle_complaint(ses_message) _check_and_queue_complaint_callback_task(complaint, notification, recipient) return try: aws_response_dict = get_aws_responses(notification_type) except KeyError: error = "{} callback failed: status {} not found".format( client_name, notification_type) return error notification_status = aws_response_dict['notification_status'] try: reference = ses_message['mail']['messageId'] notification = notifications_dao.update_notification_status_by_reference( reference, notification_status) if not notification: warning = "SES callback failed: notification either not found or already updated " \ "from sending. Status {} for notification reference {}".format(notification_status, reference) current_app.logger.warning(warning) return 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( '{} callback return status of {} for notification: {}'. format(client_name, 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'.format(client_name.lower()), datetime.utcnow(), notification.sent_at) _check_and_queue_callback_task(notification) return except KeyError: error = "SES callback failed: messageId missing" return error except ValueError: error = "{} callback failed: invalid json".format(client_name) return error