diff --git a/.ds.baseline b/.ds.baseline
index c64d22627..56c3afc7d 100644
--- a/.ds.baseline
+++ b/.ds.baseline
@@ -161,7 +161,7 @@
"filename": "app/config.py",
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
"is_verified": false,
- "line_number": 125,
+ "line_number": 123,
"is_secret": false
}
],
@@ -684,5 +684,5 @@
}
]
},
- "generated_at": "2024-11-14T15:53:44Z"
+ "generated_at": "2024-11-21T23:08:45Z"
}
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index f002bb3fc..c3ef5dcbb 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -144,6 +144,7 @@ jobs:
inputs: requirements.txt
ignore-vulns: |
PYSEC-2024-60
+ PYSEC-2022-43162
- name: Run npm audit
run: make npm-audit
diff --git a/app/assets/images/alarm.svg b/app/assets/images/alarm.svg
new file mode 100644
index 000000000..57e6180bc
--- /dev/null
+++ b/app/assets/images/alarm.svg
@@ -0,0 +1 @@
+
diff --git a/app/assets/images/alert.svg b/app/assets/images/alert.svg
new file mode 100644
index 000000000..d0d516d8f
--- /dev/null
+++ b/app/assets/images/alert.svg
@@ -0,0 +1 @@
+
diff --git a/app/assets/images/calendar.svg b/app/assets/images/calendar.svg
new file mode 100644
index 000000000..9b755e9fd
--- /dev/null
+++ b/app/assets/images/calendar.svg
@@ -0,0 +1 @@
+
diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
index da5d77bf2..cec48e82b 100644
--- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
+++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
@@ -898,7 +898,7 @@ li.linked-card:hover svg,
display: block;
}
-.about-icon-list {
+.icon-list {
display: flex;
width: 24px;
height: 24px;
@@ -908,10 +908,6 @@ li.linked-card:hover svg,
margin-right: 4px;
}
-.usa-icon-list__content{
- padding-left: 0;
-}
-
.indented-paragraph {
margin-left: calc(24px + 4px);
margin-top: 4px;
diff --git a/app/config.py b/app/config.py
index f40b46dea..146230047 100644
--- a/app/config.py
+++ b/app/config.py
@@ -91,9 +91,7 @@ class Config(object):
getenv("FEATURE_BEST_PRACTICES_ENABLED", "false") == "true"
)
- FEATURE_ABOUT_PAGE_ENABLED = (
- getenv("FEATURE_ABOUT_PAGE_ENABLED", "false") == "true"
- )
+ FEATURE_ABOUT_PAGE_ENABLED = getenv("FEATURE_ABOUT_PAGE_ENABLED", "false") == "true"
def _s3_credentials_from_env(bucket_prefix):
diff --git a/app/main/views/index.py b/app/main/views/index.py
index 370a88401..49eeae427 100644
--- a/app/main/views/index.py
+++ b/app/main/views/index.py
@@ -30,15 +30,13 @@
# Hook to check for feature flags
@main.before_request
def check_feature_flags():
- if (
- request.path.startswith("/guides/best-practices")
- and not current_app.config.get("FEATURE_BEST_PRACTICES_ENABLED", False)
+ if request.path.startswith("/guides/best-practices") and not current_app.config.get(
+ "FEATURE_BEST_PRACTICES_ENABLED", False
):
abort(404)
- if (
- request.path.startswith("/about")
- and not current_app.config.get("FEATURE_ABOUT_PAGE_ENABLED", False)
+ if request.path.startswith("/about") and not current_app.config.get(
+ "FEATURE_ABOUT_PAGE_ENABLED", False
):
abort(404)
@@ -314,6 +312,22 @@ def about_notify():
)
+@main.route("/about/security")
+def about_security():
+ return render_template(
+ "views/about/security.html",
+ navigation_links=about_notify_nav(),
+ )
+
+
+@main.route("/about/why-text-messaging")
+def why_text_messaging():
+ return render_template(
+ "views/about/why-text-messaging.html",
+ navigation_links=about_notify_nav(),
+ )
+
+
@main.route("/using-notify/guidance/create-and-send-messages")
@user_is_logged_in
def create_and_send_messages():
diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py
index b9fb7f8ae..603094cd1 100644
--- a/app/main/views/sub_navigation_dictionaries.py
+++ b/app/main/views/sub_navigation_dictionaries.py
@@ -1,3 +1,6 @@
+from flask import current_app
+
+
def features_nav():
return [
{
@@ -22,46 +25,20 @@ def features_nav():
def using_notify_nav():
- return [
- {
- "name": "Get started",
- "link": "main.get_started",
- },
- {
- "name": "Guides",
- "link": "main.best_practices",
- },
- {
- "name": "Trial mode",
- "link": "main.trial_mode_new",
- },
- {
- "name": "Tracking usage",
- "link": "main.pricing",
- },
- {
- "name": "Delivery status",
- "link": "main.message_status",
- },
- {
- "name": "Guidance",
- "link": "main.guidance_index",
- # "sub_navigation_items": [
- # {
- # "name": "Formatting",
- # "link": "main.edit_and_format_messages",
- # },
- # {
- # "name": "Send files by email",
- # "link": "main.send_files_by_email",
- # },
- # ]
- # {
- # "name": "API documentation",
- # "link": "main.documentation",
- # },
- },
+ nav_items = [
+ {"name": "Get started", "link": "main.get_started"},
+ {"name": "Guides", "link": "main.best_practices"},
+ {"name": "Trial mode", "link": "main.trial_mode_new"},
+ {"name": "Tracking usage", "link": "main.pricing"},
+ {"name": "Delivery Status", "link": "main.message_status"},
+ {"name": "Guidance", "link": "main.guidance_index"},
]
+ if not current_app.config.get("FEATURE_BEST_PRACTICES_ENABLED"):
+ nav_items = [
+ item for item in nav_items if item["link"] != "main.best_practices"
+ ]
+
+ return nav_items
def best_practices_nav():
@@ -110,7 +87,31 @@ def best_practices_nav():
def about_notify_nav():
return [
{
- "name": "About notify",
+ "name": "About Notify",
"link": "main.about_notify",
+ "sub_navigation_items": [
+ {
+ "name": "Why text messaging",
+ "link": "main.why_text_messaging",
+ "sub_sub_navigation_items": [
+ {
+ "name": "Reach people using a common method",
+ "link": "main.why_text_messaging#reach-people-using-a-common-method",
+ },
+ {
+ "name": "Improve customer experience",
+ "link": "main.why_text_messaging#improve-customer-experience",
+ },
+ {
+ "name": "What texting is best for",
+ "link": "main.why_text_messaging#what-texting-is-best-for",
+ },
+ ],
+ },
+ {
+ "name": "Security",
+ "link": "main.about_security",
+ },
+ ],
},
]
diff --git a/app/navigation.py b/app/navigation.py
index a02df484d..271d6848b 100644
--- a/app/navigation.py
+++ b/app/navigation.py
@@ -53,7 +53,7 @@ class HeaderNavigation(Navigation):
"establish_trust",
"write_for_action",
"multiple_languages",
- "benchmark_performance"
+ "benchmark_performance",
},
"using_notify": {
"get_started",
diff --git a/app/templates/base.html b/app/templates/base.html
index 369e62b81..b2b6639e9 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -66,7 +66,20 @@
diff --git a/app/templates/views/about/about.html b/app/templates/views/about/about.html
index 39dfce671..cd52709c2 100644
--- a/app/templates/views/about/about.html
+++ b/app/templates/views/about/about.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% set page_title = "About notify" %}
+{% set page_title = "About Notify" %}
{% block per_page_title %}
{{page_title}}
@@ -17,8 +17,9 @@ {{page_title}}
Meet people where they are
More effectively deliver program outcomes
Save administrative costs
- Implement 21st Century IDEA and other directives
+ Implement 21st Century
+ IDEA and other directives
Notify.gov is an easy-to-use, web-based platform. It requires no technical expertise or system integration โ users
can create an account and get started within minutes. We take the security and privacy of messaging data seriously
@@ -56,9 +57,9 @@
Product Highlights
{% for item in product_highlights %}
-
+
-
+
{{item.card_heading}}
@@ -68,10 +69,8 @@ Product Highlights
{% endfor %}
- See if Notify is right for you
- Notify.gov is a product of the Public Benefits Studio , a product accelerator inside
+
See if Notify is right for you
+ Notify.gov is a product of the Public Benefits Studio , a product accelerator inside
the federal government.
-
-
{% endblock %}
diff --git a/app/templates/views/about/security.html b/app/templates/views/about/security.html
new file mode 100644
index 000000000..9ebc0420f
--- /dev/null
+++ b/app/templates/views/about/security.html
@@ -0,0 +1,66 @@
+{% extends "base.html" %}
+
+{% set page_title = "Security" %}
+
+{% block per_page_title %}
+{{page_title}}
+{% endblock %}
+
+{% block content_column_content %}
+
+
+ {{page_title}}
+ Notify.gov is built for the needs of government agencies with fundamental system
+ security processes in place to:
+
+
+ protect user data
+ keep systems secure
+ manage risks around information
+
+
+ Notify.gov operates under a full three-year Authority-to-Operate (ATO) . This
+ federal security authorization process leverages security
+ controls provided by National Institute of Standards and Technology (NIST).
+
+
+
+ Our infrastructure runs on cloud.gov and utilizes several
+ services through Amazon Web
+ Services (AWS), including
+ AWS SNS for sending SMS
+ messages.
+
+ For more information about the Notify.gov infrastructure, contact us at notify-support@gsa.gov .
+ Data
+
+ On Notify.gov, data is encrypted both in transit and at rest. To send a message, agencies upload a spreadsheet of
+ phone numbers and other necessary data from their existing data management system.
+
+
+ Notify.gov is not a system of record, so it does not have a System of Records Notice (SORN). Agencies are
+ responsible for managing their data outside of Notify.gov.
+
+ Data retention
+
+ Any data uploads that have recipient data are held for seven calendar days; personally identifiable information
+ (PII) is never stored in Notifyโs database.
+
+ Multi-Factor Authentication
+
+ Notify.gov uses Login.gov for enhanced security.
+ Login.gov is an extra layer of security created by the government that uses multi-factor authentication and stronger
+ passwords to protect your account.
+
+
+ To access Notify.gov, users will use a Login.gov account associated with their agency (.gov) email with one of the
+ multi-factor authentication
+ methods offered through Login.gov.
+
+
+{% endblock %}
diff --git a/app/templates/views/about/why-text-messaging.html b/app/templates/views/about/why-text-messaging.html
new file mode 100644
index 000000000..6aa0887cb
--- /dev/null
+++ b/app/templates/views/about/why-text-messaging.html
@@ -0,0 +1,86 @@
+{% extends "base.html" %}
+{% set page_title = "Why text messaging" %}
+
+{% block per_page_title %}
+{{page_title}}
+{% endblock %}
+
+{% block content_column_content %}
+
+
+
+ {{page_title}}
+ Reach people using a common method
+
+ Confusing or unreceived notifications are one of the largest barriers to people getting and keeping
+ benefits. The typical ways the government communicates with people often fall short. Low income households are more
+ likely to experience housing instability, which means paper mail, already slow, can easily be missed.
+
+
+
+ Pew Research shows that nearly all adults in the US have a cell phone. Reliance on smartphones
+ for online access is especially common among Americans with lower household incomes and those with lower levels of
+ formal education. Of those earning less than $30,000 a year, 28% say their mobile phone is the sole method to
+ digitally connect.
+
+
+ This means that for many people who rely on government services, cell phones may be the most reliable place to meet
+ people where they already are.
+
+ Improve customer experience
+
+ Text messages can deliver concise information and drive an audience to take action quickly. Timely reminders sent
+ via text message have been proven to decrease re-enrollment churn and save money for administering agencies.
+
+
+ Texting not only helps programs reach people using a nearly-universal communication method, it is a cost effective
+ way to do so. With Notify.gov you can get started for free , allowing you to try out
+ texting to complement your existing communications and outreach strategies.
+
+ What texting is best for
+
+ Agencies, like you, are already using Notify.gov to text about the following programs.
+
+ {% set card_contents = [
+ {
+ "image_src": asset_url('images/calendar.svg'),
+ "card_heading": "Reminders",
+ "p_text": "In a text bubble // Your Quality Control food phone interview is on ((date)) at ((time)). Failure to
+ attend may lead to closure of your benefits. Call 1-800-222-3333 with questions.",
+ "alt_text": "reminder text example"
+ },
+ {
+ "image_src": asset_url('images/alert.svg'),
+ "card_heading": "Alerts to take action",
+ "p_text": "In a text bubble // Your household's Medicaid coverage is expiring. To keep getting Medicaid, you must
+ complete your renewal by ((date)). You can renew online at dhs.state.govโฆ",
+ "alt_text": "alerts text example"
+ },
+ {
+ "image_src": asset_url('images/alarm.svg'),
+ "card_heading": "Important status updates",
+ "p_text": "In a text bubble // Your passport has been issued at the Los Angeles Passport Agency. Please come to the
+ desk between 1:30pm and 2:30pm today to pick up your passportโฆ",
+ "alt_text": "status update text example"
+ },
+ ] %}
+ {% for item in card_contents %}
+
+
+
+
{{item.card_heading}}
+
{{item.p_text}}
+
+ {% if item.image_src %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+{% endblock %}
diff --git a/app/utils/csv.py b/app/utils/csv.py
index 4ed6d16b5..79e3535c8 100644
--- a/app/utils/csv.py
+++ b/app/utils/csv.py
@@ -103,6 +103,7 @@ def generate_notifications_csv(**kwargs):
"Carrier Response",
"Status",
"Time",
+ "Carrier",
]
for header in original_column_headers:
if header.lower() != "phone number":
@@ -118,6 +119,7 @@ def generate_notifications_csv(**kwargs):
"Carrier Response",
"Status",
"Time",
+ "Carrier",
]
yield ",".join(fieldnames) + "\n"
@@ -140,6 +142,7 @@ def generate_notifications_csv(**kwargs):
notification["provider_response"],
notification["status"],
preferred_tz_created_at,
+ notification["carrier"],
]
for header in original_column_headers:
if header.lower() != "phone number":
@@ -158,6 +161,7 @@ def generate_notifications_csv(**kwargs):
notification["provider_response"],
notification["status"],
preferred_tz_created_at,
+ notification["carrier"],
]
yield Spreadsheet.from_rows([map(str, values)]).as_csv_data
diff --git a/deploy-config/production.yml b/deploy-config/production.yml
index 42a681cb4..9f5cffc89 100644
--- a/deploy-config/production.yml
+++ b/deploy-config/production.yml
@@ -1,6 +1,6 @@
env: production
instances: 2
-memory: 1.5G
+memory: 2G
command: newrelic-admin run-program gunicorn -c /home/vcap/app/gunicorn_config.py application
public_admin_route: beta.notify.gov
cloud_dot_gov_route: notify.app.cloud.gov
diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py
index af7b7a158..f469f2586 100644
--- a/tests/app/test_navigation.py
+++ b/tests/app/test_navigation.py
@@ -18,6 +18,7 @@
Navigation.get_endpoint_with_blueprint,
{
"about_notify",
+ "about_security",
"accept_invite",
"accept_org_invite",
"accessibility_statement",
@@ -258,6 +259,7 @@
"view_template_version",
"view_template_versions",
"who_its_for",
+ "why_text_messaging",
"write_for_action",
},
)
diff --git a/tests/app/utils/test_csv.py b/tests/app/utils/test_csv.py
index d603fcd0e..db4b6a0ec 100644
--- a/tests/app/utils/test_csv.py
+++ b/tests/app/utils/test_csv.py
@@ -58,6 +58,7 @@ def _get(
"to": recipient,
"recipient": recipient,
"client_reference": "ref 1234",
+ "carrier": "AT&T Mobility",
}
for i in range(rows)
],
@@ -88,15 +89,15 @@ def get_notifications_csv_mock(
(
None,
[
- "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time\n",
- "8005555555,foo,,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n",
+ "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time,Carrier\n",
+ "8005555555,foo,,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern,AT&T Mobility\r\n",
],
),
(
"Anne Example",
[
- "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time\n",
- "8005555555,foo,Anne Example,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n", # noqa
+ "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time,Carrier\n",
+ "8005555555,foo,Anne Example,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern,AT&T Mobility\r\n", # noqa
],
),
],
@@ -135,6 +136,7 @@ def test_generate_notifications_csv_without_job(
"Carrier Response",
"Status",
"Time",
+ "Carrier",
],
[
"8005555555",
@@ -144,6 +146,7 @@ def test_generate_notifications_csv_without_job(
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 AM US/Eastern",
+ "AT&T Mobility",
],
),
(
@@ -159,6 +162,7 @@ def test_generate_notifications_csv_without_job(
"Carrier Response",
"Status",
"Time",
+ "Carrier",
"a",
"b",
"c",
@@ -171,6 +175,7 @@ def test_generate_notifications_csv_without_job(
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 AM US/Eastern",
+ "AT&T Mobility",
"๐",
"๐",
"๐ฆ",
@@ -189,6 +194,7 @@ def test_generate_notifications_csv_without_job(
"Carrier Response",
"Status",
"Time",
+ "Carrier",
"a",
"b",
"c",
@@ -201,6 +207,7 @@ def test_generate_notifications_csv_without_job(
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 AM US/Eastern",
+ "AT&T Mobility",
"๐,๐",
"๐,๐",
"๐ฆ",
diff --git a/tests/end_to_end/test_best_practices_content_pages.py b/tests/end_to_end/test_best_practices_content_pages.py
index 962100de3..031e4baef 100644
--- a/tests/end_to_end/test_best_practices_content_pages.py
+++ b/tests/end_to_end/test_best_practices_content_pages.py
@@ -11,7 +11,7 @@
def test_best_practices_side_menu(authenticated_page):
page = authenticated_page
- page.goto(f"{E2E_TEST_URI}/best-practices")
+ page.goto(f"{E2E_TEST_URI}/guides/best-practices")
page.wait_for_load_state("domcontentloaded")
check_axe_report(page)
@@ -58,7 +58,7 @@ def test_best_practices_side_menu(authenticated_page):
def test_breadcrumbs_best_practices(authenticated_page):
page = authenticated_page
- page.goto(f"{E2E_TEST_URI}/best-practices")
+ page.goto(f"{E2E_TEST_URI}/guides/best-practices")
page.wait_for_load_state("domcontentloaded")
check_axe_report(page)
diff --git a/urls.js b/urls.js
index 0440430ec..2279b5a79 100644
--- a/urls.js
+++ b/urls.js
@@ -8,7 +8,7 @@ const sublinks = [
{ label: 'Trial Mode', path: '/using-notify/trial-mode' },
{ label: 'Pricing', path: '/using-notify/pricing' },
{ label: 'Delivery Status', path: '/using-notify/delivery-status' },
- { label: 'Guidance', path: '/using-notify/guidance' },
+ { label: 'Guidance', path: '/guides/using-notify/guidance' },
{ label: 'Features', path: '/features' },
{ label: 'Roadmap', path: '/features/roadmap' },
{ label: 'Security', path: '/features/security' },