def update_user_attribute(user_id): user_to_update = get_user_by_id(user_id=user_id) req_json = request.get_json() if 'updated_by' in req_json: updated_by = get_user_by_id(user_id=req_json.pop('updated_by')) else: updated_by = None update_dct, errors = user_update_schema_load_json.load(req_json) if errors: raise InvalidRequest(errors, status_code=400) save_user_attribute(user_to_update, update_dict=update_dct) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) # Alert user that account change took place user_alert_dct = update_dct.copy() user_alert_dct.pop('blocked', None) user_alert_dct.pop('current_session_id', None) if not updated_by and user_alert_dct: _update_alert(user_to_update, user_alert_dct) # Alert that team member edit user if updated_by: if 'email_address' in update_dct: template = dao_get_template_by_id( current_app.config['TEAM_MEMBER_EDIT_EMAIL_TEMPLATE_ID']) recipient = user_to_update.email_address reply_to = template.service.get_default_reply_to_email_address() elif 'mobile_number' in update_dct: template = dao_get_template_by_id( current_app.config['TEAM_MEMBER_EDIT_MOBILE_TEMPLATE_ID']) recipient = user_to_update.mobile_number reply_to = template.service.get_default_sms_sender() else: return jsonify(data=user_to_update.serialize()), 200 saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=recipient, service=service, personalisation={ 'name': user_to_update.name, 'servicemanagername': updated_by.name, 'email address': user_to_update.email_address }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=reply_to) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify(data=user_to_update.serialize()), 200
def send_notification_to_service_users(service_id, template_id, personalisation=None, include_user_fields=None): personalisation = personalisation or {} include_user_fields = include_user_fields or [] template = dao_get_template_by_id(template_id) service = dao_fetch_service_by_id(service_id) active_users = dao_fetch_active_users_for_service(service.id) notify_service = dao_fetch_service_by_id( current_app.config['NOTIFY_SERVICE_ID']) for user in active_users: personalisation = _add_user_fields(user, personalisation, include_user_fields) notification = persist_notification( template_id=template.id, template_version=template.version, recipient=user.email_address if template.template_type == EMAIL_TYPE else user.mobile_number, service=notify_service, personalisation=personalisation, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=notify_service.get_default_reply_to_email_address()) send_notification_to_queue(notification, False, queue=QueueNames.NOTIFY)
def create_job(service_id): dao_fetch_service_by_id(service_id) data = request.get_json() data.update({ "service": service_id }) template = dao_get_template_by_id(data['template']) errors = unarchived_template_schema.validate({'archived': template.archived}) if errors: raise InvalidRequest(errors, status_code=400) data.update({"template_version": template.version}) job = job_schema.load(data).data if job.scheduled_for: job.job_status = JOB_STATUS_SCHEDULED dao_create_job(job) if job.job_status == JOB_STATUS_PENDING: process_job.apply_async([str(job.id)], queue="process-job") job_json = job_schema.dump(job).data job_json['statistics'] = [] return jsonify(data=job_json), 201
def can_letter_job_be_cancelled(job): template = dao_get_template_by_id(job.template_id) if template.template_type != LETTER_TYPE: return ( False, "Only letter jobs can be cancelled through this endpoint. This is not a letter job.", ) notifications = Notification.query.filter(Notification.job_id == job.id).all() count_notifications = len(notifications) if job.job_status != JOB_STATUS_FINISHED or count_notifications != job.notification_count: return ( False, "We are still processing these letters, please try again in a minute.", ) count_cancellable_notifications = len([n for n in notifications if n.status in CANCELLABLE_JOB_LETTER_STATUSES]) if count_cancellable_notifications != job.notification_count or not letter_can_be_cancelled( NOTIFICATION_CREATED, job.created_at ): return ( False, "It’s too late to cancel sending, these letters have already been sent.", ) return True, None
def send_user_sms_code(user_id): user_to_send_to = get_user_by_id(user_id=user_id) verify_code, errors = request_verify_code_schema.load(request.get_json()) secret_code = create_secret_code() create_user_code(user_to_send_to, secret_code, SMS_TYPE) mobile = user_to_send_to.mobile_number if verify_code.get('to', None) is None else verify_code.get('to') sms_code_template_id = current_app.config['SMS_CODE_TEMPLATE_ID'] sms_code_template = dao_get_template_by_id(sms_code_template_id) notify_service_id = current_app.config['NOTIFY_SERVICE_ID'] saved_notification = persist_notification( template_id=sms_code_template_id, template_version=sms_code_template.version, recipient=mobile, service_id=notify_service_id, personalisation={'verify_code': secret_code}, notification_type=SMS_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL ) # Assume that we never want to observe the Notify service's research mode # setting for this notification - we still need to be able to log into the # admin even if we're doing user research using this service: send_notification_to_queue(saved_notification, False, queue='notify') return jsonify({}), 204
def send_user_confirm_new_email(user_id): user_to_send_to = get_user_by_id(user_id=user_id) email, errors = email_data_request_schema.load(request.get_json()) if errors: raise InvalidRequest(message=errors, status_code=400) template = dao_get_template_by_id(current_app.config['CHANGE_EMAIL_CONFIRMATION_TEMPLATE_ID']) notify_service_id = current_app.config['NOTIFY_SERVICE_ID'] saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=email['email'], service_id=notify_service_id, personalisation={ 'name': user_to_send_to.name, 'url': _create_confirmation_url(user=user_to_send_to, email_address=email['email']), 'feedback_url': current_app.config['ADMIN_BASE_URL'] + '/feedback' }, notification_type=EMAIL_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL ) send_notification_to_queue(saved_notification, False, queue='notify') return jsonify({}), 204
def test_send_sms_should_use_template_version_from_notification_not_latest( sample_template, mocker): db_notification = create_notification( template=sample_template, to_field='+447234123123', status='created', reply_to_text=sample_template.service.get_default_sms_sender()) mocker.patch('app.mmg_client.send_sms') version_on_notification = sample_template.version # Change the template from app.dao.templates_dao import dao_update_template, dao_get_template_by_id sample_template.content = sample_template.content + " another version of the template" dao_update_template(sample_template) t = dao_get_template_by_id(sample_template.id) assert t.version > version_on_notification send_to_providers.send_sms_to_provider(db_notification) mmg_client.send_sms.assert_called_once_with( to=validate_and_format_phone_number("+447234123123"), content="Sample service: This is a template:\nwith a newline", reference=str(db_notification.id), sender=current_app.config['FROM_NUMBER']) persisted_notification = notifications_dao.get_notification_by_id( db_notification.id) assert persisted_notification.to == db_notification.to assert persisted_notification.template_id == sample_template.id assert persisted_notification.template_version == version_on_notification assert persisted_notification.template_version != sample_template.version assert persisted_notification.status == 'sending' assert not persisted_notification.personalisation
def create_invited_user(service_id): request_json = request.get_json() invited_user, errors = invited_user_schema.load(request_json) save_invited_user(invited_user) template = dao_get_template_by_id( current_app.config['INVITATION_EMAIL_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=invited_user.email_address, service=service, personalisation={ 'user_name': invited_user.from_user.name, 'service_name': invited_user.service.name, 'url': invited_user_url( invited_user.id, request_json.get('invite_link_host'), ), }, notification_type=EMAIL_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=invited_user.from_user.email_address) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify(data=invited_user_schema.dump(invited_user).data), 201
def send_user_confirm_new_email(user_id): user_to_send_to = get_user_by_id(user_id=user_id) email, errors = email_data_request_schema.load(request.get_json()) if errors: raise InvalidRequest(message=errors, status_code=400) template = dao_get_template_by_id(current_app.config['CHANGE_EMAIL_CONFIRMATION_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=email['email'], service=service, personalisation={ 'name': user_to_send_to.name, 'url': _create_confirmation_url(user=user_to_send_to, email_address=email['email']), 'feedback_url': current_app.config['ADMIN_BASE_URL'] + '/support/ask-question-give-feedback' }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=service.get_default_reply_to_email_address() ) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify({}), 204
def send_support_email(user_id): to, errors = support_email_data_schema.load(request.get_json()) template = dao_get_template_by_id( current_app.config['CONTACT_US_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=to['email'], service=service, personalisation={ 'user': to['email'], 'message': to['message'], 'sender_email': to["sender"] if "sender" in to else "", 'support_type': to["support_type"] if "support_type" in to else "" }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=service.get_default_reply_to_email_address()) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify({}), 204
def send_new_user_email_verification(user_id): # when registering, we verify all users' email addresses using this function user_to_send_to = get_user_by_id(user_id=user_id) template = dao_get_template_by_id(current_app.config['NEW_USER_EMAIL_VERIFICATION_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=user_to_send_to.email_address, service=service, personalisation={ 'name': user_to_send_to.name, 'url': _create_verification_url(user_to_send_to) }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=service.get_default_reply_to_email_address() ) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify({}), 204
def send_user_reset_password(): email, errors = email_data_request_schema.load(request.get_json()) user_to_send_to = get_user_by_email(email['email']) if user_to_send_to.blocked: return jsonify({'message': 'cannot reset password: user blocked'}), 400 template = dao_get_template_by_id(current_app.config['PASSWORD_RESET_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=email['email'], service=service, personalisation={ 'user_name': user_to_send_to.name, 'url': _create_reset_password_url(user_to_send_to.email_address) }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=service.get_default_reply_to_email_address() ) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify({}), 204
def _update_alert(user_to_update, changes=None): service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) template = dao_get_template_by_id(current_app.config['ACCOUNT_CHANGE_TEMPLATE_ID']) recipient = user_to_update.email_address reply_to = template.service.get_default_reply_to_email_address() change_type_en = "" change_type_fr = "" if changes: change_type_en = update_dct_to_str(changes, 'EN') change_type_fr = update_dct_to_str(changes, 'FR') saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=recipient, service=service, personalisation={ 'base_url': Config.ADMIN_BASE_URL, 'contact_us_url': f'{Config.ADMIN_BASE_URL}/support/ask-question-give-feedback', 'change_type_en': change_type_en, 'change_type_fr': change_type_fr, }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=reply_to ) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY)
def process_incomplete_job(job_id): job = dao_get_job_by_id(job_id) last_notification_added = dao_get_last_notification_added_for_job_id(job_id) if last_notification_added: resume_from_row = last_notification_added.job_row_number else: resume_from_row = -1 # The first row in the csv with a number is row 0 current_app.logger.info("Resuming job {} from row {}".format(job_id, resume_from_row)) db_template = dao_get_template_by_id(job.template_id, job.template_version) TemplateClass = get_template_class(db_template.template_type) template = TemplateClass(db_template.__dict__) for row in RecipientCSV( s3.get_job_from_s3(str(job.service_id), str(job.id)), template_type=template.template_type, placeholders=template.placeholders ).rows: if row.index > resume_from_row: process_row(row, template, job, job.service) job_complete(job, resumed=True)
def fix_billable_units(): query = Notification.query.filter( Notification.notification_type == SMS_TYPE, Notification.status != NOTIFICATION_CREATED, Notification.sent_at == None, # noqa Notification.billable_units == 0, Notification.key_type != KEY_TYPE_TEST, ) for notification in query.all(): template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=notification.service.name, show_prefix=notification.service.prefix_sms, ) print("Updating notification: {} with {} billable_units".format( notification.id, template.fragment_count)) Notification.query.filter(Notification.id == notification.id).update( {"billable_units": template.fragment_count}) db.session.commit() print("End fix_billable_units")
def create_2fa_code(template_id, user_to_send_to, secret_code, recipient, personalisation): template = dao_get_template_by_id(template_id) # save the code in the VerifyCode table create_user_code(user_to_send_to, secret_code, template.template_type) reply_to = None if template.template_type == SMS_TYPE: reply_to = template.service.get_default_sms_sender() elif template.template_type == EMAIL_TYPE: reply_to = template.service.get_default_reply_to_email_address() saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=recipient, service=template.service, personalisation=personalisation, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=reply_to ) # Assume that we never want to observe the Notify service's research mode # setting for this notification - we still need to be able to log into the # admin even if we're doing user research using this service: send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY)
def test_send_email_should_use_template_version_from_job_not_latest(sample_email_template, mocker): notification = _notification_json(sample_email_template, 'my_email@my_email.com') version_on_notification = sample_email_template.version # Change the template from app.dao.templates_dao import dao_update_template, dao_get_template_by_id sample_email_template.content = sample_email_template.content + " another version of the template" mocker.patch('app.celery.provider_tasks.deliver_email.apply_async') dao_update_template(sample_email_template) t = dao_get_template_by_id(sample_email_template.id) assert t.version > version_on_notification now = datetime.utcnow() send_email( sample_email_template.service_id, uuid.uuid4(), encryption.encrypt(notification), now.strftime(DATETIME_FORMAT) ) persisted_notification = Notification.query.one() assert persisted_notification.to == 'my_email@my_email.com' assert persisted_notification.template_id == sample_email_template.id assert persisted_notification.template_version == version_on_notification assert persisted_notification.created_at == now assert not persisted_notification.sent_at assert persisted_notification.status == 'created' assert not persisted_notification.sent_by assert persisted_notification.notification_type == 'email' provider_tasks.deliver_email.apply_async.assert_called_once_with([str(persisted_notification.id)], queue='send-email')
def send_branding_request(user_id): to, errors = branding_request_data_schema.load(request.get_json()) template = dao_get_template_by_id( current_app.config['BRANDING_REQUEST_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=to['email'], service=service, personalisation={ 'email': to['email'], 'serviceID': to['serviceID'], 'service_name': to['service_name'], 'filename': to['filename'] }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=service.get_default_reply_to_email_address()) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify({}), 204
def send_already_registered_email(user_id): to, errors = email_data_request_schema.load(request.get_json()) template = dao_get_template_by_id( current_app.config['ALREADY_REGISTERED_EMAIL_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=to['email'], service=service, personalisation={ 'signin_url': current_app.config['ADMIN_BASE_URL'] + '/sign-in', 'forgot_password_url': current_app.config['ADMIN_BASE_URL'] + '/forgot-password', 'feedback_url': current_app.config['ADMIN_BASE_URL'] + '/support' }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=service.get_default_reply_to_email_address()) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify({}), 204
def send_sms_to_provider(notification): service = dao_fetch_service_by_id(notification.service_id) provider = provider_to_use(SMS_TYPE, notification.id) if notification.status == 'created': template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, sender=service.sms_sender ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: send_sms_response.apply_async( (provider.get_name(), str(notification.id), notification.to), queue='research-mode' ) notification.billable_units = 0 else: provider.send_sms( to=validate_and_format_phone_number(notification.to), content=str(template), reference=str(notification.id), sender=service.sms_sender ) notification.billable_units = template.fragment_count notification.sent_at = datetime.utcnow() notification.sent_by = provider.get_name() notification.status = 'sending' dao_update_notification(notification) current_app.logger.info( "SMS {} sent to provider at {}".format(notification.id, notification.sent_at) ) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("sms.total-time", delta_milliseconds)
def get_recipient_csv_and_template_and_sender_id(job): db_template = dao_get_template_by_id(job.template_id, job.template_version) template = db_template._as_utils_template() contents, meta_data = s3.get_job_and_metadata_from_s3(service_id=str(job.service_id), job_id=str(job.id)) recipient_csv = RecipientCSV(contents, template=template) return recipient_csv, template, meta_data.get("sender_id")
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None): notification = encryption.decrypt(encrypted_notification) service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification["template"], version=notification["template_version"]) if sender_id: reply_to_text = dao_get_service_sms_senders_by_id( service_id, sender_id).sms_sender else: reply_to_text = template.get_reply_to_text() if not service_allowed_to_send_to(notification["to"], service, KEY_TYPE_NORMAL): current_app.logger.debug( "SMS {} failed as restricted service".format(notification_id)) return check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) try: # this task is used by two main things... process_job and process_sms_or_email_notification # if the data is not present in the encrypted data then fallback on whats needed for process_job saved_notification = persist_notification( notification_id=notification.get("id", notification_id), template_id=notification["template"], template_version=notification["template_version"], recipient=notification["to"], service=service, personalisation=notification.get("personalisation"), notification_type=SMS_TYPE, simulated=notification.get("simulated", None), api_key_id=notification.get("api_key", None), key_type=notification.get("key_type", KEY_TYPE_NORMAL), created_at=datetime.utcnow(), job_id=notification.get("job", None), job_row_number=notification.get("row_number", None), reply_to_text=reply_to_text, ) send_notification_to_queue( saved_notification, service.research_mode, queue=notification.get("queue") or template.queue_to_use(), ) current_app.logger.debug("SMS {} created at {} for job {}".format( saved_notification.id, saved_notification.created_at, notification.get("job", None), )) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(SMS_TYPE, notification.international) template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: update_notification_to_sending(notification, provider) send_sms_response(provider.get_name(), str(notification.id), notification.to) else: try: provider.send_sms(to=validate_and_format_phone_number( notification.to, international=notification.international), content=str(template), reference=str(notification.id), sender=notification.reply_to_text) except Exception as e: notification.billable_units = template.fragment_count dao_update_notification(notification) dao_reduce_sms_provider_priority( provider.get_name(), time_threshold=timedelta(minutes=1)) raise e else: notification.billable_units = template.fragment_count update_notification_to_sending(notification, provider) delta_seconds = (datetime.utcnow() - notification.created_at).total_seconds() statsd_client.timing("sms.total-time", delta_seconds) if notification.key_type == KEY_TYPE_TEST: statsd_client.timing("sms.test-key.total-time", delta_seconds) else: statsd_client.timing("sms.live-key.total-time", delta_seconds) if str(service.id) in current_app.config.get( 'HIGH_VOLUME_SERVICE'): statsd_client.timing("sms.live-key.high-volume.total-time", delta_seconds) else: statsd_client.timing("sms.live-key.not-high-volume.total-time", delta_seconds)
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(SMS_TYPE, notification.id, notification.international) current_app.logger.debug( "Starting sending SMS {} to provider at {}".format( notification.id, datetime.utcnow())) template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.billable_units = 0 update_notification(notification, provider) try: send_sms_response(provider.get_name(), str(notification.id), notification.to) except HTTPError: # when we retry, we only do anything if the notification is in created - it's currently in sending, # so set it back so that we actually attempt the callback again notification.sent_at = None notification.sent_by = None notification.status = NOTIFICATION_CREATED dao_update_notification(notification) raise else: try: provider.send_sms(to=validate_and_format_phone_number( notification.to, international=notification.international), content=str(template), reference=str(notification.id), sender=notification.reply_to_text) except Exception as e: dao_toggle_sms_provider(provider.name) raise e else: notification.billable_units = template.fragment_count update_notification(notification, provider, notification.international) current_app.logger.debug("SMS {} sent to provider {} at {}".format( notification.id, provider.get_name(), notification.sent_at)) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("sms.total-time", delta_milliseconds)
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == "created": provider = provider_to_use( SMS_TYPE, notification.id, notification.international, notification.reply_to_text, ) template_dict = dao_get_template_by_id( notification.template_id, notification.template_version).__dict__ template = SMSMessageTemplate( template_dict, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = send_sms_response(provider.get_name(), notification.to) update_notification_to_sending(notification, provider) else: try: reference = provider.send_sms( to=validate_and_format_phone_number( notification.to, international=notification.international), content=str(template), reference=str(notification.id), sender=notification.reply_to_text, ) except Exception as e: notification.billable_units = template.fragment_count dao_update_notification(notification) dao_toggle_sms_provider(provider.name) raise e else: notification.reference = reference notification.billable_units = template.fragment_count update_notification_to_sending(notification, provider) # Record StatsD stats to compute SLOs statsd_client.timing_with_dates("sms.total-time", notification.sent_at, notification.created_at) statsd_key = f"sms.process_type-{template_dict['process_type']}" statsd_client.timing_with_dates(statsd_key, notification.sent_at, notification.created_at) statsd_client.incr(statsd_key)
def save_letter( self, service_id, notification_id, encrypted_notification, ): notification = encryption.decrypt(encrypted_notification) # we store the recipient as just the first item of the person's address recipient = notification["personalisation"]["addressline1"] service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification["template"], version=notification["template_version"]) check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) try: # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING saved_notification = persist_notification( template_id=notification["template"], template_version=notification["template_version"], template_postage=template.postage, recipient=recipient, service=service, personalisation=notification["personalisation"], notification_type=LETTER_TYPE, api_key_id=notification.get("api_key", None), key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification["job"], job_row_number=notification["row_number"], notification_id=notification_id, reference=create_random_identifier(), reply_to_text=template.get_reply_to_text(), status=status, ) if not service.research_mode: send_notification_to_queue(saved_notification, service.research_mode) elif current_app.config["NOTIFY_ENVIRONMENT"] in [ "preview", "development" ]: research_mode_tasks.create_fake_letter_response_file.apply_async( (saved_notification.reference, ), queue=QueueNames.RESEARCH_MODE) else: update_notification_status_by_reference( saved_notification.reference, "delivered") current_app.logger.debug("Letter {} created at {}".format( saved_notification.id, saved_notification.created_at)) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def save_letter( self, service_id, notification_id, encrypted_notification, ): notification = encryption.decrypt(encrypted_notification) postal_address = PostalAddress.from_personalisation( Columns(notification['personalisation'])) service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification['template'], version=notification['template_version']) try: # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING saved_notification = persist_notification( template_id=notification['template'], template_version=notification['template_version'], postage=postal_address.postage if postal_address.international else template.postage, recipient=postal_address.normalised, service=service, personalisation=notification['personalisation'], notification_type=LETTER_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification['job'], job_row_number=notification['row_number'], notification_id=notification_id, reference=create_random_identifier(), reply_to_text=template.get_reply_to_text(), status=status) if not service.research_mode: letters_pdf_tasks.get_pdf_for_templated_letter.apply_async( [str(saved_notification.id)], queue=QueueNames.CREATE_LETTERS_PDF) elif current_app.config['NOTIFY_ENVIRONMENT'] in [ 'preview', 'development' ]: research_mode_tasks.create_fake_letter_response_file.apply_async( (saved_notification.reference, ), queue=QueueNames.RESEARCH_MODE) else: update_notification_status_by_reference( saved_notification.reference, 'delivered') current_app.logger.debug("Letter {} created at {}".format( saved_notification.id, saved_notification.created_at)) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def send_email_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(EMAIL_TYPE, notification.id) current_app.logger.debug( "Starting sending EMAIL {} to provider at {}".format(notification.id, datetime.utcnow()) ) template_dict = dao_get_template_by_id(notification.template_id, notification.template_version).__dict__ html_email = HTMLEmailTemplate( template_dict, values=notification.personalisation, **get_html_email_options(service) ) plain_text_email = PlainTextEmailTemplate( template_dict, values=notification.personalisation ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: reference = str(create_uuid()) notification.billable_units = 0 notification.reference = reference update_notification(notification, provider) send_email_response(reference, notification.to) else: from_address = '"{}" <{}@{}>'.format(service.name, service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']) email_reply_to = notification.reply_to_text reference, status = provider.send_email( from_address, validate_and_format_email_address(notification.to), plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=validate_and_format_email_address(email_reply_to) if email_reply_to else None, ) notification.reference = reference update_notification(notification, provider, status=status) current_app.logger.debug("SENT_MAIL: {} -- {}".format( validate_and_format_email_address(notification.to), str(plain_text_email)) ) current_app.logger.debug( "Email {} sent to provider at {}".format(notification.id, notification.sent_at) ) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("email.total-time", delta_milliseconds)
def send_email_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(EMAIL_TYPE) template_dict = dao_get_template_by_id( notification.template_id, notification.template_version).__dict__ html_email = HTMLEmailTemplate(template_dict, values=notification.personalisation, **get_html_email_options(service)) plain_text_email = PlainTextEmailTemplate( template_dict, values=notification.personalisation) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = str(create_uuid()) update_notification_to_sending(notification, provider) send_email_response(notification.reference, notification.to) else: from_address = '"{}" <{}@{}>'.format( service.name, service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']) email_reply_to = notification.reply_to_text reference = provider.send_email( from_address, validate_and_format_email_address(notification.to), plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=validate_and_format_email_address( email_reply_to) if email_reply_to else None, ) notification.reference = reference update_notification_to_sending(notification, provider) delta_seconds = (datetime.utcnow() - notification.created_at).total_seconds() if notification.key_type == KEY_TYPE_TEST: statsd_client.timing("email.test-key.total-time", delta_seconds) else: statsd_client.timing("email.live-key.total-time", delta_seconds) if str(service.id) in current_app.config.get( 'HIGH_VOLUME_SERVICE'): statsd_client.timing("email.live-key.high-volume.total-time", delta_seconds) else: statsd_client.timing( "email.live-key.not-high-volume.total-time", delta_seconds)
def process_job(job_id): start = datetime.utcnow() job = dao_get_job_by_id(job_id) if job.job_status != "pending": return service = job.service if __sending_limits_for_job_exceeded(service, job, job_id): return job.job_status = "in progress" dao_update_job(job) template = Template(dao_get_template_by_id(job.template_id, job.template_version).__dict__) for row_number, recipient, personalisation in RecipientCSV( s3.get_job_from_s3(str(service.id), str(job_id)), template_type=template.template_type, placeholders=template.placeholders, ).enumerated_recipients_and_personalisation: encrypted = encryption.encrypt( { "template": str(template.id), "template_version": job.template_version, "job": str(job.id), "to": recipient, "row_number": row_number, "personalisation": dict(personalisation), } ) if template.template_type == SMS_TYPE: send_sms.apply_async( (str(job.service_id), create_uuid(), encrypted, datetime.utcnow().strftime(DATETIME_FORMAT)), queue="db-sms" if not service.research_mode else "research-mode", ) if template.template_type == EMAIL_TYPE: send_email.apply_async( (str(job.service_id), create_uuid(), encrypted, datetime.utcnow().strftime(DATETIME_FORMAT)), queue="db-email" if not service.research_mode else "research-mode", ) finished = datetime.utcnow() job.job_status = "finished" job.processing_started = start job.processing_finished = finished dao_update_job(job) current_app.logger.info( "Job {} created at {} started at {} finished at {}".format(job_id, job.created_at, start, finished) )
def test_update_set_process_type_on_template(client, sample_template): auth_header = create_authorization_header() data = {'process_type': 'priority'} resp = client.post('/service/{}/template/{}'.format( sample_template.service_id, sample_template.id), data=json.dumps(data), headers=[('Content-Type', 'application/json'), auth_header]) assert resp.status_code == 200 template = dao_get_template_by_id(sample_template.id) assert template.process_type == 'priority'
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(SMS_TYPE, notification, notification.international) template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = create_uuid() update_notification_to_sending(notification, provider) send_sms_response(provider.get_name(), str(notification.id), notification.to, notification.reference) else: try: reference = provider.send_sms( to=validate_and_format_phone_number( notification.to, international=notification.international), content=str(template), reference=str(notification.id), sender=notification.reply_to_text) except Exception as e: notification.billable_units = template.fragment_count dao_update_notification(notification) dao_toggle_sms_provider(provider.name) raise e else: notification.billable_units = template.fragment_count notification.reference = reference update_notification_to_sending(notification, provider) current_app.logger.info( f"Saved provider reference: {reference} for notification id: {notification.id}" ) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("sms.total-time", delta_milliseconds)
def get_recipient_csv_and_template_and_sender_id(job): db_template = dao_get_template_by_id(job.template_id, job.template_version) TemplateClass = get_template_class(db_template.template_type) template = TemplateClass(db_template.__dict__) contents, meta_data = s3.get_job_and_metadata_from_s3(service_id=str( job.service_id), job_id=str(job.id)) recipient_csv = RecipientCSV(file_data=contents, template_type=template.template_type, placeholders=template.placeholders) return recipient_csv, template, meta_data.get("sender_id")
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None): notification = encryption.decrypt(encrypted_notification) service = dao_fetch_service_by_id(service_id) template = dao_get_template_by_id(notification['template'], version=notification['template_version']) if sender_id: reply_to_text = dao_get_service_sms_senders_by_id(service_id, sender_id).sms_sender else: reply_to_text = template.get_reply_to_text() if not service_allowed_to_send_to(notification['to'], service, KEY_TYPE_NORMAL): current_app.logger.debug( "SMS {} failed as restricted service".format(notification_id) ) return try: saved_notification = persist_notification( template_id=notification['template'], template_version=notification['template_version'], recipient=notification['to'], service=service, personalisation=notification.get('personalisation'), notification_type=SMS_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL, created_at=datetime.utcnow(), job_id=notification.get('job', None), job_row_number=notification.get('row_number', None), notification_id=notification_id, reply_to_text=reply_to_text ) provider_tasks.deliver_sms.apply_async( [str(saved_notification.id)], queue=QueueNames.SEND_SMS if not service.research_mode else QueueNames.RESEARCH_MODE ) current_app.logger.debug( "SMS {} created at {} for job {}".format( saved_notification.id, saved_notification.created_at, notification.get('job', None)) ) except SQLAlchemyError as e: handle_exception(self, notification, notification_id, e)
def _process_for_status(notification_status, client_name, provider_reference, detailed_status_code=None): # record stats if client_name == 'Twilio': notification = notifications_dao.update_notification_status_by_reference( reference=provider_reference, status=notification_status) else: notification = notifications_dao.update_notification_status_by_id( notification_id=provider_reference, status=notification_status, sent_by=client_name.lower(), detailed_status_code=detailed_status_code) if not notification: return statsd_client.incr('callback.{}.{}'.format(client_name.lower(), notification_status)) if notification.sent_at: statsd_client.timing_with_dates( 'callback.{}.elapsed-time'.format(client_name.lower()), datetime.utcnow(), notification.sent_at) if notification.billable_units == 0: service = notification.service template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) notification.billable_units = template.fragment_count notifications_dao.dao_update_notification(notification) if notification_status != NOTIFICATION_PENDING: service_callback_api = get_service_delivery_status_callback_api_for_service( service_id=notification.service_id) # queue callback task only if the service_callback_api exists if service_callback_api: encrypted_notification = create_delivery_status_callback_data( notification, service_callback_api) send_delivery_status_to_service.apply_async( [str(notification.id), encrypted_notification], queue=QueueNames.CALLBACKS)
def test_update_does_not_create_new_version_when_there_is_no_change(client, sample_template): auth_header = create_authorization_header() data = { 'template_type': sample_template.template_type, 'content': sample_template.content, } resp = client.post('/service/{}/template/{}'.format(sample_template.service_id, sample_template.id), data=json.dumps(data), headers=[('Content-Type', 'application/json'), auth_header]) assert resp.status_code == 200 template = dao_get_template_by_id(sample_template.id) assert template.version == 1
def test_update_does_not_create_new_version_when_there_is_no_change(client, sample_template): auth_header = create_authorization_header() data = { 'template_type': sample_template.template_type, 'content': sample_template.content, } resp = client.post('/service/{}/template/{}'.format(sample_template.service_id, sample_template.id), data=json.dumps(data), headers=[('Content-Type', 'application/json'), auth_header]) assert resp.status_code == 200 template = dao_get_template_by_id(sample_template.id) assert template.version == 1
def send_email_to_provider(notification): service = dao_fetch_service_by_id(notification.service_id) provider = provider_to_use(EMAIL_TYPE, notification.id) if notification.status == 'created': template_dict = dao_get_template_by_id(notification.template_id, notification.template_version).__dict__ html_email = HTMLEmailTemplate( template_dict, values=notification.personalisation, **get_html_email_options(service) ) plain_text_email = PlainTextEmailTemplate( template_dict, values=notification.personalisation ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: reference = str(create_uuid()) send_email_response.apply_async( (provider.get_name(), reference, notification.to), queue='research-mode' ) notification.billable_units = 0 else: from_address = '"{}" <{}@{}>'.format(service.name, service.email_from, current_app.config['NOTIFY_EMAIL_DOMAIN']) reference = provider.send_email( from_address, notification.to, plain_text_email.subject, body=str(plain_text_email), html_body=str(html_email), reply_to_address=service.reply_to_email_address, ) notification.reference = reference notification.sent_at = datetime.utcnow() notification.sent_by = provider.get_name(), notification.status = 'sending' dao_update_notification(notification) current_app.logger.info( "Email {} sent to provider at {}".format(notification.id, notification.sent_at) ) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("email.total-time", delta_milliseconds)
def send_user_reset_password(): email, errors = email_data_request_schema.load(request.get_json()) user_to_send_to = get_user_by_email(email['email']) template = dao_get_template_by_id(current_app.config['PASSWORD_RESET_TEMPLATE_ID']) message = { 'template': str(template.id), 'template_version': template.version, 'to': user_to_send_to.email_address, 'personalisation': { 'user_name': user_to_send_to.name, 'url': _create_reset_password_url(user_to_send_to.email_address) } } send_email.apply_async([current_app.config['NOTIFY_SERVICE_ID'], str(uuid.uuid4()), encryption.encrypt(message), datetime.utcnow().strftime(DATETIME_FORMAT)], queue='notify') return jsonify({}), 204
def send_already_registered_email(user_id): to, errors = email_data_request_schema.load(request.get_json()) template = dao_get_template_by_id(current_app.config['ALREADY_REGISTERED_EMAIL_TEMPLATE_ID']) message = { 'template': str(template.id), 'template_version': template.version, 'to': to['email'], 'personalisation': { 'signin_url': current_app.config['ADMIN_BASE_URL'] + '/sign-in', 'forgot_password_url': current_app.config['ADMIN_BASE_URL'] + '/forgot-password', 'feedback_url': current_app.config['ADMIN_BASE_URL'] + '/feedback' } } send_email.apply_async(( current_app.config['NOTIFY_SERVICE_ID'], str(uuid.uuid4()), encryption.encrypt(message), datetime.utcnow().strftime(DATETIME_FORMAT) ), queue='notify') return jsonify({}), 204
def test_send_sms_should_use_template_version_from_notification_not_latest( notify_db, notify_db_session, sample_template, mocker): db_notification = sample_notification(notify_db, notify_db_session, template=sample_template, to_field='+447234123123', status='created') mocker.patch('app.mmg_client.send_sms') mocker.patch('app.mmg_client.get_name', return_value="mmg") version_on_notification = sample_template.version # Change the template from app.dao.templates_dao import dao_update_template, dao_get_template_by_id sample_template.content = sample_template.content + " another version of the template" dao_update_template(sample_template) t = dao_get_template_by_id(sample_template.id) assert t.version > version_on_notification send_to_providers.send_sms_to_provider( db_notification ) mmg_client.send_sms.assert_called_once_with( to=format_phone_number(validate_phone_number("+447234123123")), content="Sample service: This is a template:\nwith a newline", reference=str(db_notification.id), sender=None ) persisted_notification = notifications_dao.get_notification_by_id(db_notification.id) assert persisted_notification.to == db_notification.to assert persisted_notification.template_id == sample_template.id assert persisted_notification.template_version == version_on_notification assert persisted_notification.template_version != sample_template.version assert persisted_notification.status == 'sending' assert not persisted_notification.personalisation
def send_user_email_verification(user_id): user_to_send_to = get_user_by_id(user_id=user_id) secret_code = create_secret_code() create_user_code(user_to_send_to, secret_code, 'email') template = dao_get_template_by_id(current_app.config['EMAIL_VERIFY_CODE_TEMPLATE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=user_to_send_to.email_address, service_id=current_app.config['NOTIFY_SERVICE_ID'], personalisation={ 'name': user_to_send_to.name, 'url': _create_verification_url(user_to_send_to, secret_code) }, notification_type=EMAIL_TYPE, api_key_id=None, key_type=KEY_TYPE_NORMAL ) send_notification_to_queue(saved_notification, False, queue="notify") return jsonify({}), 204