mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-19 05:58:53 -04:00
merge from main
This commit is contained in:
@@ -30,9 +30,11 @@ from app.models import (
|
||||
SMS_TYPE,
|
||||
)
|
||||
from app.notifications.process_notifications import persist_notification
|
||||
from app.notifications.validators import check_service_over_total_message_limit
|
||||
from app.serialised_models import SerialisedService, SerialisedTemplate
|
||||
from app.service.utils import service_allowed_to_send_to
|
||||
from app.utils import DATETIME_FORMAT
|
||||
from app.v2.errors import TotalRequestsError
|
||||
|
||||
|
||||
@notify_celery.task(name="process-job")
|
||||
@@ -64,6 +66,9 @@ def process_job(job_id, sender_id=None):
|
||||
)
|
||||
return
|
||||
|
||||
if __total_sending_limits_for_job_exceeded(service, job, job_id):
|
||||
return
|
||||
|
||||
recipient_csv, template, sender_id = get_recipient_csv_and_template_and_sender_id(
|
||||
job
|
||||
)
|
||||
@@ -145,6 +150,25 @@ def process_row(row, template, job, service, sender_id=None):
|
||||
return notification_id
|
||||
|
||||
|
||||
def __total_sending_limits_for_job_exceeded(service, job, job_id):
|
||||
try:
|
||||
total_sent = check_service_over_total_message_limit(KEY_TYPE_NORMAL, service)
|
||||
if total_sent + job.notification_count > service.total_message_limit:
|
||||
raise TotalRequestsError(service.total_message_limit)
|
||||
else:
|
||||
return False
|
||||
except TotalRequestsError:
|
||||
job.job_status = "sending limits exceeded"
|
||||
job.processing_finished = datetime.utcnow()
|
||||
dao_update_job(job)
|
||||
current_app.logger.error(
|
||||
"Job {} size {} error. Total sending limits {} exceeded".format(
|
||||
job_id, job.notification_count, service.message_limit
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@notify_celery.task(bind=True, name="save-sms", max_retries=5, default_retry_delay=300)
|
||||
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None):
|
||||
notification = encryption.decrypt(encrypted_notification)
|
||||
|
||||
@@ -772,12 +772,23 @@ def create_user_jwt(token):
|
||||
|
||||
def _update_template(id, name, template_type, content, subject):
|
||||
template = Template.query.filter_by(id=id).first()
|
||||
if not template:
|
||||
template = Template(id=id)
|
||||
template.service_id = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553"
|
||||
template.created_by_id = "6af522d0-2915-4e52-83a3-3690455a5fe6"
|
||||
db.session.add(template)
|
||||
template.name = name
|
||||
template.template_type = template_type
|
||||
template.content = "\n".join(content)
|
||||
template.subject = subject
|
||||
|
||||
history = TemplateHistory.query.filter_by(id=id).first()
|
||||
if not history:
|
||||
history = TemplateHistory(id=id)
|
||||
history.service_id = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553"
|
||||
history.created_by_id = "6af522d0-2915-4e52-83a3-3690455a5fe6"
|
||||
history.version = 1
|
||||
db.session.add(history)
|
||||
history.name = name
|
||||
history.template_type = template_type
|
||||
history.content = "\n".join(content)
|
||||
@@ -792,3 +803,27 @@ def update_templates():
|
||||
data = json.load(f)
|
||||
for d in data:
|
||||
_update_template(d["id"], d["name"], d["type"], d["content"], d["subject"])
|
||||
|
||||
|
||||
@notify_command(name="create-new-service")
|
||||
@click.option("-n", "--name", required=True, prompt=True)
|
||||
@click.option("-l", "--message_limit", required=False, default=40000)
|
||||
@click.option("-r", "--restricted", required=False, default=False)
|
||||
@click.option("-e", "--email_from", required=True)
|
||||
@click.option("-c", "--created_by_id", required=True)
|
||||
def create_new_service(name, message_limit, restricted, email_from, created_by_id):
|
||||
data = {
|
||||
"name": name,
|
||||
"message_limit": message_limit,
|
||||
"restricted": restricted,
|
||||
"email_from": email_from,
|
||||
"created_by_id": created_by_id,
|
||||
}
|
||||
|
||||
service = Service(**data)
|
||||
try:
|
||||
db.session.add(service)
|
||||
db.session.commit()
|
||||
except IntegrityError:
|
||||
print("duplicate service", service.name)
|
||||
db.session.rollback()
|
||||
|
||||
@@ -282,6 +282,8 @@ class Config(object):
|
||||
|
||||
FREE_SMS_TIER_FRAGMENT_COUNT = 250000
|
||||
|
||||
TOTAL_MESSAGE_LIMIT = 250000
|
||||
|
||||
DAILY_MESSAGE_LIMIT = notifications_utils.DAILY_MESSAGE_LIMIT
|
||||
|
||||
HIGH_VOLUME_SERVICE = json.loads(getenv("HIGH_VOLUME_SERVICE", "[]"))
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
"name": "Example text message template",
|
||||
"type": "sms",
|
||||
"subject": "",
|
||||
"content": ["Hi, I’m trying out U.S. Notify. Today is ((day of week)) and my favorite color is ((color))."]
|
||||
"content": ["Hi, I’m trying out Notify.gov. Today is ((day of week)) and my favorite color is ((color))."]
|
||||
},
|
||||
{
|
||||
"id": "4f46df42-f795-4cc4-83bb-65ca312f49cc",
|
||||
"name": "Notify invitation email",
|
||||
"type": "email",
|
||||
"subject": "((user_name)) has invited you to collaborate on ((service_name)) on U.S. Notify",
|
||||
"content": ["((user_name)) has invited you to collaborate on ((service_name)) on U.S. Notify.",
|
||||
"subject": "((user_name)) has invited you to collaborate on ((service_name)) on Notify.gov",
|
||||
"content": ["((user_name)) has invited you to collaborate on ((service_name)) on Notify.gov.",
|
||||
"",
|
||||
"",
|
||||
"U.S. Notify makes it easy to keep people updated by helping you send text messages and emails.",
|
||||
"Notify.gov makes it easy to keep people updated by helping you send text messages and emails.",
|
||||
"",
|
||||
"",
|
||||
"Click this link to create an account on U.S. Notify:",
|
||||
"Click this link to create an account on Notify.gov:",
|
||||
"",
|
||||
"((url))",
|
||||
"",
|
||||
@@ -29,17 +29,17 @@
|
||||
"name": "Notify SMS verify code",
|
||||
"type": "sms",
|
||||
"subject": "",
|
||||
"content": ["((verify_code)) is your U.S. Notify authentication code"]
|
||||
"content": ["((verify_code)) is your Notify.gov authentication code"]
|
||||
},
|
||||
{
|
||||
"id": "474e9242-823b-4f99-813d-ed392e7f1201",
|
||||
"name": "Notify password reset email",
|
||||
"type": "email",
|
||||
"subject": "Reset your U.S. Notify password",
|
||||
"subject": "Reset your Notify.gov password",
|
||||
"content": ["Hi ((user_name)),",
|
||||
"",
|
||||
"",
|
||||
"We received a request to reset your password on U.S. Notify.",
|
||||
"We received a request to reset your password on Notify.gov.",
|
||||
"",
|
||||
"",
|
||||
"If you didn’t request this email, you can ignore it – your password has not been changed.",
|
||||
@@ -51,11 +51,11 @@
|
||||
"id": "299726d2-dba6-42b8-8209-30e1d66ea164",
|
||||
"name": "Notify email verify code",
|
||||
"type": "email",
|
||||
"subject": "Sign in to U.S. Notify",
|
||||
"subject": "Sign in to Notify.gov",
|
||||
"content": ["Hi ((name)),",
|
||||
"",
|
||||
"",
|
||||
"To sign in to U.S. Notify please open this link:",
|
||||
"To sign in to Notify.gov please open this link:",
|
||||
"",
|
||||
"",
|
||||
"((url))"]
|
||||
@@ -64,21 +64,21 @@
|
||||
"id": "ece42649-22a8-4d06-b87f-d52d5d3f0a27",
|
||||
"name": "Notify email verification code",
|
||||
"type": "email",
|
||||
"subject": "Confirm U.S. Notify registration",
|
||||
"subject": "Confirm Notify.gov registration",
|
||||
"content": ["Hi ((name)),",
|
||||
"",
|
||||
"",
|
||||
"To complete your registration for U.S. Notify please click the link below",
|
||||
"To complete your registration for Notify.gov please click the link below",
|
||||
"",
|
||||
"",
|
||||
"((url))"]
|
||||
},
|
||||
{
|
||||
"id": "0880fbb1-a0c6-46f0-9a8e-36c986381ceb",
|
||||
"name": "Your U.S. Notify account",
|
||||
"name": "Your Notify.gov account",
|
||||
"type": "email",
|
||||
"subject": "Your U.S. Notify account",
|
||||
"content": ["You already have a U.S. Notify account with this email address.",
|
||||
"subject": "Your Notify.gov account",
|
||||
"content": ["You already have a Notify.gov account with this email address.",
|
||||
"",
|
||||
"",
|
||||
"Sign in here: ((signin_url))",
|
||||
@@ -89,20 +89,20 @@
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"If you didn’t try to register for a U.S. Notify account recently, please let us know here: ((feedback_url))"]
|
||||
"If you didn’t try to register for a Notify.gov account recently, please let us know here: ((feedback_url))"]
|
||||
},
|
||||
{
|
||||
"id": "eb4d9930-87ab-4aef-9bce-786762687884",
|
||||
"name": "Confirm new email address",
|
||||
"type": "email",
|
||||
"subject": "Confirm your email address for U.S. Notify",
|
||||
"subject": "Confirm your email address for Notify.gov",
|
||||
"content": ["Hi ((name)),","","","Click this link to confirm your new email address:",
|
||||
"",
|
||||
"",
|
||||
"((url))",
|
||||
"",
|
||||
"",
|
||||
"If you didn’t try to change the email address for your U.S. Notify account, let us know here:",
|
||||
"If you didn’t try to change the email address for your Notify.gov account, let us know here:",
|
||||
"",
|
||||
"",
|
||||
"((feedback_url))"]
|
||||
@@ -111,11 +111,11 @@
|
||||
"id": "618185c6-3636-49cd-b7d2-6f6f5eb3bdde",
|
||||
"name": "Automated \"You’re now live\" message",
|
||||
"type": "email",
|
||||
"subject": "((service name)) is now live on U.S. Notify",
|
||||
"subject": "((service name)) is now live on Notify.gov",
|
||||
"content": ["Hi ((name)),",
|
||||
"",
|
||||
"",
|
||||
"((service name)) is now live on U.S. Notify.",
|
||||
"((service name)) is now live on Notify.gov.",
|
||||
"",
|
||||
"",
|
||||
"You can send up to ((message limit)) messages per day.",
|
||||
@@ -158,87 +158,87 @@
|
||||
"",
|
||||
"Thanks",
|
||||
"",
|
||||
"U.S. Notify team"]
|
||||
"Notify.gov team"]
|
||||
},
|
||||
{
|
||||
"id": "203566f0-d835-47c5-aa06-932439c86573",
|
||||
"name": "Notify organization invitation email",
|
||||
"type": "email",
|
||||
"subject": "((user_name)) has invited you to collaborate on ((organization_name)) on U.S. Notify",
|
||||
"content": ["((user_name)) has invited you to collaborate on ((organization_name)) on U.S. Notify.","","","U.S. Notify makes it easy to keep people updated by helping you send text messages and emails.","","","Open this link to create an account on U.S. Notify:","","((url))","","","This invitation will stop working at midnight tomorrow. This is to keep ((organization_name)) secure."]
|
||||
"subject": "((user_name)) has invited you to collaborate on ((organization_name)) on Notify.gov",
|
||||
"content": ["((user_name)) has invited you to collaborate on ((organization_name)) on Notify.gov.","","","Notify.gov makes it easy to keep people updated by helping you send text messages and emails.","","","Open this link to create an account on Notify.gov:","","((url))","","","This invitation will stop working at midnight tomorrow. This is to keep ((organization_name)) secure."]
|
||||
},
|
||||
{
|
||||
"id": "c73f1d71-4049-46d5-a647-d013bdeca3f0",
|
||||
"name": "Email address changed by service manager",
|
||||
"type": "email",
|
||||
"subject": "Your U.S. Notify email address has changed",
|
||||
"content": ["Dear ((name)),","","","((servicemanagername)) changed your Notify account email address to:","","","((email address))","","","You’ll need to use this email address next time you sign in.","","","Thanks","","","U.S. Notify team"]
|
||||
"subject": "Your Notify.gov email address has changed",
|
||||
"content": ["Dear ((name)),","","","((servicemanagername)) changed your Notify account email address to:","","","((email address))","","","You’ll need to use this email address next time you sign in.","","","Thanks","","","Notify.gov team"]
|
||||
},
|
||||
{
|
||||
"id": "8a31520f-4751-4789-8ea1-fe54496725eb",
|
||||
"name": "Phone number changed by service manager",
|
||||
"type": "sms",
|
||||
"subject": "",
|
||||
"content": ["Your mobile number was changed by ((servicemanagername)). Next time you sign in, your U.S. Notify authentication code will be sent to this phone."]
|
||||
"content": ["Your mobile number was changed by ((servicemanagername)). Next time you sign in, your Notify.gov authentication code will be sent to this phone."]
|
||||
},
|
||||
{
|
||||
"id": "a42f1d17-9404-46d5-a647-d013bdfca3e1",
|
||||
"name": "Verify email reply-to address for a service",
|
||||
"type": "email",
|
||||
"subject": "Your U.S. Notify reply-to email address",
|
||||
"content": ["Hi,","","","This address has been provided as a reply-to email address for a U.S. Notify account.","","Any replies from users to emails they receive through U.S. Notify will come back to this email address.","","","This is just a quick check to make sure the address is valid.","","","No need to reply.","","","Thanks","","","U.S. Notify team"]
|
||||
"subject": "Your Notify.gov reply-to email address",
|
||||
"content": ["Hi,","","","This address has been provided as a reply-to email address for a Notify.gov account.","","Any replies from users to emails they receive through Notify.gov will come back to this email address.","","","This is just a quick check to make sure the address is valid.","","","No need to reply.","","","Thanks","","","Notify.gov team"]
|
||||
},
|
||||
{
|
||||
"id": "4fd2e43c-309b-4e50-8fb8-1955852d9d71",
|
||||
"name": "MOU Signed By Receipt",
|
||||
"type": "email",
|
||||
"subject": "You’ve accepted the U.S. Notify data sharing and financial agreement",
|
||||
"subject": "You’ve accepted the Notify.gov data sharing and financial agreement",
|
||||
"content": [
|
||||
"Hi ((signed_by_name)),",
|
||||
"",
|
||||
"((org_name)) has accepted the U.S. Notify data sharing and financial agreement. ",
|
||||
"((org_name)) has accepted the Notify.gov data sharing and financial agreement. ",
|
||||
"",
|
||||
"If you need another copy of the agreement you can download it here: ((mou_link))",
|
||||
"",
|
||||
"",
|
||||
"Thanks,",
|
||||
"U.S. Notify team"
|
||||
"Notify.gov team"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c20206d5-bf03-4002-9a90-37d5032d9e84",
|
||||
"name": "MOU Signed On Behalf Of Receipt - Signed by",
|
||||
"type": "email",
|
||||
"subject": "You’ve accepted the U.S. Notify data sharing and financial agreement",
|
||||
"subject": "You’ve accepted the Notify.gov data sharing and financial agreement",
|
||||
"content": [
|
||||
"Hi ((signed_by_name)),",
|
||||
"",
|
||||
"((org_name)) has accepted the U.S. Notify data sharing and financial agreement. We’ve emailed ((on_behalf_of_name)) to let them know too.",
|
||||
"((org_name)) has accepted the Notify.gov data sharing and financial agreement. We’ve emailed ((on_behalf_of_name)) to let them know too.",
|
||||
"",
|
||||
"If you need another copy of the agreement you can download it here: ((mou_link))",
|
||||
"",
|
||||
"",
|
||||
"Thanks,",
|
||||
"U.S. Notify team"
|
||||
"Notify.gov team"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "522b6657-5ca5-4368-a294-6b527703bd0b",
|
||||
"name": "MOU Signed On Behalf Of Receipt - On Behalf Of",
|
||||
"type": "email",
|
||||
"subject": "((org_name)) has accepted the U.S. Notify data sharing and financial agreement",
|
||||
"subject": "((org_name)) has accepted the Notify.gov data sharing and financial agreement",
|
||||
"content": [
|
||||
"Hi ((on_behalf_of_name)),",
|
||||
"",
|
||||
"((signed_by_name)) has accepted the U.S. Notify data sharing and financial agreement on your behalf, for ((org_name)).",
|
||||
"((signed_by_name)) has accepted the Notify.gov data sharing and financial agreement on your behalf, for ((org_name)).",
|
||||
"",
|
||||
"U.S. Notify lets teams in the public sector send text messages and emails. It’s built and run by a team in the TTS Public Benefits Studio (part of GSA).",
|
||||
"Notify.gov lets teams in the public sector send text messages and emails. It’s built and run by a team in the TTS Public Benefits Studio (part of GSA).",
|
||||
"",
|
||||
"If you need another copy of the agreement you can download it here: ((mou_link))",
|
||||
"",
|
||||
"",
|
||||
"Thanks,",
|
||||
"U.S. Notify team"
|
||||
"Notify.gov team"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -268,7 +268,7 @@
|
||||
"",
|
||||
"Thanks",
|
||||
"",
|
||||
"U.S. Notify team"
|
||||
"Notify.gov team"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -487,6 +487,9 @@ class Service(db.Model, Versioned):
|
||||
db.Boolean, index=False, unique=False, nullable=False, default=True
|
||||
)
|
||||
message_limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False)
|
||||
total_message_limit = db.Column(
|
||||
db.BigInteger, index=False, unique=False, nullable=False
|
||||
)
|
||||
restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False)
|
||||
email_from = db.Column(db.Text, index=False, unique=True, nullable=False)
|
||||
created_by_id = db.Column(
|
||||
|
||||
@@ -81,7 +81,7 @@ def persist_notification(
|
||||
reply_to_text=None,
|
||||
billable_units=None,
|
||||
document_download_count=None,
|
||||
updated_at=None
|
||||
updated_at=None,
|
||||
):
|
||||
current_app.logger.info("Persisting notification")
|
||||
|
||||
@@ -150,7 +150,7 @@ def persist_notification(
|
||||
current_app.logger.info("Redis total limit cache key does exist")
|
||||
redis_store.incr(total_key)
|
||||
current_app.logger.info(
|
||||
"Redis total limit cache key has been incremented"
|
||||
f"Redis total limit cache key has been incremented to {redis_store.get(total_key)}"
|
||||
)
|
||||
current_app.logger.info(
|
||||
"{} {} created at {}".format(
|
||||
|
||||
@@ -3,6 +3,7 @@ from notifications_utils import SMS_CHAR_COUNT_LIMIT
|
||||
from notifications_utils.clients.redis import (
|
||||
daily_total_cache_key,
|
||||
rate_limit_cache_key,
|
||||
total_limit_cache_key,
|
||||
)
|
||||
from notifications_utils.recipients import (
|
||||
get_international_phone_info,
|
||||
@@ -44,6 +45,27 @@ def check_service_over_api_rate_limit(service, api_key):
|
||||
raise RateLimitError(rate_limit, interval, api_key.key_type)
|
||||
|
||||
|
||||
def check_service_over_total_message_limit(key_type, service):
|
||||
if key_type == KEY_TYPE_TEST or not current_app.config["REDIS_ENABLED"]:
|
||||
return 0
|
||||
|
||||
cache_key = total_limit_cache_key(service.id)
|
||||
service_stats = redis_store.get(cache_key)
|
||||
if service_stats is None:
|
||||
# first message of the day, set the cache to 0 and the expiry to 24 hours
|
||||
service_stats = 0
|
||||
redis_store.set(cache_key, service_stats, ex=86400)
|
||||
return service_stats
|
||||
if int(service_stats) >= service.total_message_limit:
|
||||
current_app.logger.warning(
|
||||
"service {} has been rate limited for total use sent {} limit {}".format(
|
||||
service.id, int(service_stats), service.total_message_limit
|
||||
)
|
||||
)
|
||||
raise TotalRequestsError(service.total_message_limit)
|
||||
return int(service_stats)
|
||||
|
||||
|
||||
def check_application_over_retention_limit(key_type, service):
|
||||
if key_type == KEY_TYPE_TEST or not current_app.config["REDIS_ENABLED"]:
|
||||
return 0
|
||||
|
||||
@@ -327,6 +327,7 @@ class DetailedServiceSchema(BaseSchema):
|
||||
"inbound_sms",
|
||||
"jobs",
|
||||
"message_limit",
|
||||
"total_message_limit",
|
||||
"permissions",
|
||||
"rate_limit",
|
||||
"reply_to_email_addresses",
|
||||
@@ -735,6 +736,7 @@ class ServiceHistorySchema(ma.Schema):
|
||||
updated_at = FlexibleDateTime()
|
||||
active = fields.Boolean()
|
||||
message_limit = fields.Integer()
|
||||
total_message_limit = fields.Integer()
|
||||
restricted = fields.Boolean()
|
||||
email_from = fields.String()
|
||||
created_by_id = fields.UUID()
|
||||
|
||||
@@ -79,6 +79,7 @@ class SerialisedService(SerialisedModel):
|
||||
"contact_link",
|
||||
"email_from",
|
||||
"message_limit",
|
||||
"total_message_limit",
|
||||
"permissions",
|
||||
"rate_limit",
|
||||
"restricted",
|
||||
|
||||
@@ -216,6 +216,8 @@ def create_service():
|
||||
raise InvalidRequest(errors, status_code=400)
|
||||
data.pop("service_domain", None)
|
||||
|
||||
data["total_message_limit"] = current_app.config["TOTAL_MESSAGE_LIMIT"]
|
||||
|
||||
# validate json with marshmallow
|
||||
service_schema.load(data)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from app.notifications.process_notifications import (
|
||||
send_notification_to_queue,
|
||||
)
|
||||
from app.notifications.validators import (
|
||||
check_service_over_total_message_limit,
|
||||
validate_and_format_recipient,
|
||||
validate_template,
|
||||
)
|
||||
@@ -44,6 +45,8 @@ def send_one_off_notification(service_id, post_data):
|
||||
|
||||
validate_template(template.id, personalisation, service, template.template_type)
|
||||
|
||||
check_service_over_total_message_limit(KEY_TYPE_NORMAL, service)
|
||||
|
||||
validate_and_format_recipient(
|
||||
send_to=post_data["to"],
|
||||
key_type=KEY_TYPE_NORMAL,
|
||||
|
||||
Reference in New Issue
Block a user