diff --git a/.ds.baseline b/.ds.baseline index 36b7821a7..0aff1ada1 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -161,7 +161,7 @@ "filename": "app/config.py", "hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc", "is_verified": false, - "line_number": 118, + "line_number": 121, "is_secret": false } ], @@ -425,7 +425,7 @@ "filename": "deploy-config/sandbox.yml", "hashed_secret": "113151dd10316fcb0d5507b6215d78e2f3fe9e54", "is_verified": false, - "line_number": 8, + "line_number": 9, "is_secret": false } ], @@ -684,5 +684,5 @@ } ] }, - "generated_at": "2024-10-07T20:44:40Z" + "generated_at": "2024-10-29T19:28:03Z" } diff --git a/.gitignore b/.gitignore index aaa99d325..59b996de8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.XLSX ## Non user files allowed to be commited +!app/assets/pdf/tcpa_overview.pdf !tests/test_pdf_files/no_eof_marker.pdf !tests/test_pdf_files/multi_page_pdf.pdf !tests/test_pdf_files/big.pdf diff --git a/app/assets/images/best-practices-card-sprite.svg b/app/assets/images/best-practices-card-sprite.svg new file mode 100644 index 000000000..0591b73fb --- /dev/null +++ b/app/assets/images/best-practices-card-sprite.svg @@ -0,0 +1,119 @@ + diff --git a/app/assets/images/call-center.svg b/app/assets/images/call-center.svg new file mode 100644 index 000000000..f6e78b53a --- /dev/null +++ b/app/assets/images/call-center.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/community-org.svg b/app/assets/images/community-org.svg new file mode 100644 index 000000000..f03a16146 --- /dev/null +++ b/app/assets/images/community-org.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/grouping.svg b/app/assets/images/grouping.svg new file mode 100644 index 000000000..b79d7a18f --- /dev/null +++ b/app/assets/images/grouping.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/phone_grey.svg b/app/assets/images/phone_grey.svg new file mode 100644 index 000000000..0ef4f9541 --- /dev/null +++ b/app/assets/images/phone_grey.svg @@ -0,0 +1,3 @@ + diff --git a/app/assets/images/project-management.svg b/app/assets/images/project-management.svg new file mode 100644 index 000000000..f88a96e66 --- /dev/null +++ b/app/assets/images/project-management.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/snap-renewal.jpg b/app/assets/images/snap-renewal.jpg new file mode 100644 index 000000000..bd09b6cd1 Binary files /dev/null and b/app/assets/images/snap-renewal.jpg differ diff --git a/app/assets/images/website.svg b/app/assets/images/website.svg new file mode 100644 index 000000000..1ca731f45 --- /dev/null +++ b/app/assets/images/website.svg @@ -0,0 +1 @@ + diff --git a/app/assets/pdf/tcpa_overview.pdf b/app/assets/pdf/tcpa_overview.pdf new file mode 100644 index 000000000..61bf4a835 Binary files /dev/null and b/app/assets/pdf/tcpa_overview.pdf differ diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 61cd543ef..fe98aed09 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -671,3 +671,229 @@ details form { border: 1px solid #face00; border-radius: 7px; } + +.usa-prose.margin-top-0 { + margin-top: 0; +} + +ol.best-practices-list { + counter-reset: item; + list-style-type: none; + padding-left: 0; +} + +ol.best-practices-list.set-two { + counter-reset: item 6; +} + +ol.best-practices-list.set-three { + counter-reset: item 10; +} + +ol.best-practices-list li { + counter-increment: item; + margin-bottom: 15px; + position: relative; + padding-left: 40px; +} + +ol.best-practices-list li::before { + content: counter(item); + background-color: #005ea2; + color: white; + width: 32px; + height: 32px; + border-radius: 50%; + display: inline-flex; + justify-content: center; + align-items: center; + line-height: 1; + position: absolute; + left: 0; + top: 0; +} + +li.best-practices { + padding-bottom: 50px; +} + +div.best-practices { + height: 400px +} + +$do-dont-color-do: "green-cool-50v"; +$do-dont-color-dont: "red-cool-50v"; +$do-dont-color-border: "gray-cool-20"; +$do-dont-padding: 3; +$do-dont-top-bar-width: 1; + +.do-dont { + display: flex; + height: 100%; + padding-top: units(2); +} + +.do-dont__do, +.do-dont__dont { + background-color: color("white"); + border: 1px solid color($do-dont-color-border); + flex: 1 0 0; + height: 100%; + position: relative; +} + +.do-dont__do { + padding: 0rem; +} + +.do-dont__do:before, +.do-dont__dont:before { + content: ""; + position: absolute; + height: units($do-dont-top-bar-width); + width: calc(100% + 2px); + top: -1px; + left: -1px; +} + +.do-dont__do:before { + background-color: color($do-dont-color-do); +} + +.do-dont__dont:before { + background-color: color($do-dont-color-dont); +} + +.do-dont__heading { + // @include typeset("lang", "lg", 2); + align-items: center; + border-bottom: 1px solid color($do-dont-color-border); + display: flex; + font-weight: fw("bold"); + margin: 0; + padding: units(4) units($do-dont-padding) units($do-dont-padding); + + .usa-icon { + @include u-square(3); + margin-right: units(0.5); + } +} + +.do-dont__do .do-dont__heading { + color: color($do-dont-color-do); +} + +.do-dont__dont .do-dont__heading { + color: color($do-dont-color-dont); +} + +.do-dont__content { + padding: 0 units($do-dont-padding) units($do-dont-padding) units($do-dont-padding); + + ul { + margin-bottom: 0; + overflow-wrap: anywhere; + } + + li+li { + margin-top: units(1); + } +} + +.site-note { + background-color: #dfe1e2; + max-width: 72ex; + padding: .5rem; + border-radius: 2px; +} + +.svg-circle-icon { + display: inline-block; + margin: 0; + padding: 0; + vertical-align: text-bottom; + margin-right: -2px; +} + +.height-phone-lg { + max-height: 500px; +} + +@media (max-width: 758px) { + .best-practices-flex-container { + flex-direction: column; + } +} + +.linked-content:hover { + cursor: pointer; + transform: scale(1.05); + transition: transform 0.3s ease, background-color 0.3s ease, +} + +li.linked-card:hover > div:first-child:hover { + border-color: #005ea2; +} + +li.linked-card:hover h4, +li.linked-card:hover p, +li.linked-card:hover svg, +.best-practices_card_img:hover{ + color: #005ea2; +} + +.best-practices_card_img { + display: block; + width: 50%; + height: auto; + margin: 0 auto; +} + +.best-practices_link { + text-decoration: none; +} + +.usa-link--downloadable { + position: relative; +} + +.usa-link--downloadable::after { + background-image: url(../img/material-icons/download.svg); + background-repeat: no-repeat; + background-position: center center; + background-size: 1.75ex 1.75ex; + height: 1.75ex; + width: 1.75ex; + content: ""; + display: inline; + margin-top: .7ex; + margin-left: 2px; + padding-left: 1.75ex; + vertical-align: middle; +} + +.usa-link--downloadable::after { + background: 0 0; + background-color: currentColor; + -webkit-mask-image: url(../img/material-icons/download.svg), linear-gradient(transparent, transparent); + mask-image: url(../img/material-icons/download.svg), linear-gradient(transparent, transparent); + -webkit-mask-position: center center; + mask-position: center center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 1.75ex 1.75ex; + mask-size: 1.75ex 1.75ex; +} + +.usa-sidenav__sublist { + display: none; +} + +.usa-sidenav__item:hover .usa-sidenav__sublist, +.usa-sidenav__item:focus-within .usa-sidenav__sublist { + display: block; +} + +.usa-sidenav__item a { + display: block; +} diff --git a/app/config.py b/app/config.py index dece2728d..249451c66 100644 --- a/app/config.py +++ b/app/config.py @@ -87,6 +87,9 @@ class Config(object): "tts-benefits-studio@gsa.gov", ], } + FEATURE_BEST_PRACTICES_ENABLED = ( + getenv("FEATURE_BEST_PRACTICES_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 4b0b57783..8b965991c 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -9,11 +9,27 @@ from app import redis_client, status_api_client from app.formatters import apply_html_class, convert_markdown_template from app.main import main from app.main.views.pricing import CURRENT_SMS_RATE -from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav +from app.main.views.sub_navigation_dictionaries import ( + best_practices_nav, + features_nav, + using_notify_nav, +) from app.utils.user import user_is_logged_in from notifications_utils.url_safe_token import generate_token +# Hook to check for guidance routes +@main.before_request +def check_guidance_feature(): + current_app.logger.warning("best practices 1234") + current_app.logger.warning(current_app.config["FEATURE_BEST_PRACTICES_ENABLED"]) + if ( + request.path.startswith("/best-practices") + and not current_app.config["FEATURE_BEST_PRACTICES_ENABLED"] + ): + abort(404) + + @main.route("/") def index(): if current_user and current_user.is_authenticated: @@ -189,12 +205,78 @@ def trial_mode_new(): ) +@main.route("/best-practices") +@user_is_logged_in +def best_practices(): + return render_template( + "views/best-practices/best-practices.html", + navigation_links=best_practices_nav(), + ) + + +@main.route("/best-practices/clear-goals") +@user_is_logged_in +def clear_goals(): + return render_template( + "views/best-practices/clear-goals.html", + navigation_links=best_practices_nav(), + ) + + +@main.route("/best-practices/rules-and-regulations") +@user_is_logged_in +def rules_and_regulations(): + return render_template( + "views/best-practices/rules-and-regulations.html", + navigation_links=best_practices_nav(), + ) + + +@main.route("/best-practices/establish-trust") +@user_is_logged_in +def establish_trust(): + return render_template( + "views/best-practices/establish-trust.html", + navigation_links=best_practices_nav(), + ) + + +@main.route("/best-practices/write-for-action") +@user_is_logged_in +def write_for_action(): + return render_template( + "views/best-practices/write-for-action.html", + navigation_links=best_practices_nav(), + ) + + +@main.route("/best-practices/multiple-languages") +@user_is_logged_in +def multiple_languages(): + return render_template( + "views/best-practices/multiple-languages.html", + navigation_links=best_practices_nav(), + ) + + +@main.route("/best-practices/benchmark-performance") +@user_is_logged_in +def benchmark_performance(): + return render_template( + "views/best-practices/benchmark-performance.html", + navigation_links=best_practices_nav(), + ) + + @main.route("/using-notify/guidance") @user_is_logged_in def guidance_index(): return render_template( "views/guidance/index.html", navigation_links=using_notify_nav(), + feature_best_practices_enabled=current_app.config[ + "FEATURE_BEST_PRACTICES_ENABLED" + ], ) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index 5c7cf26ba..0689c198d 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -58,3 +58,46 @@ def using_notify_nav(): # }, }, ] + + +def best_practices_nav(): + return [ + { + "name": "Best Practices", + "link": "main.best_practices", + }, + { + "name": "Clear goals", + "link": "main.clear_goals", + }, + { + "name": "Rules and regulations", + "link": "main.rules_and_regulations", + }, + { + "name": "Establish trust", + "link": "main.establish_trust", + "sub_navigation_items": [ + { + "name": "Get the word out", + "link": "main.establish_trust#get-the-word-out", + }, + { + "name": "As people receive texts", + "link": "main.establish_trust#as-people-receive-texts", + }, + ], + }, + { + "name": "Write for action", + "link": "main.write_for_action", + }, + { + "name": "Multiple languages", + "link": "main.multiple_languages", + }, + { + "name": "Benchmark performance", + "link": "main.benchmark_performance", + }, + ] diff --git a/app/templates/base.html b/app/templates/base.html index c535fafe2..369e62b81 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -53,9 +53,30 @@
+ {{item.p_text|safe}} +
+Learn how effective your texting program can be.
+ ++ Several factors determine a messaging campaign’s success, but the performance of previous projects can help set + expectations. Get to know what other government texting initiatives have done as a way to benchmark your own + performance. +
++ When the Center on Budget and Policy Priorities studied WIC, they found key learnings about the + quantity of messages delivered, how people engage with messages, and how they take action. +
+ +Benchmark: 80% of texts are + successfully delivered +
++ You may discover that some numbers are temporarily or permanently unavailable due to service being + discontinued, + numbers changing, or being a landline. +
+Benchmark: Engagement rates with texts ranged from 17% + to 26%
++ The highest rate of engagement with a text comes within hours of sending. Engagement rates include any kind + of + action taken due to a text, including replying “STOP” to prevent future texts. +
+Benchmark: Requesting appointments after receiving texts ranged + from 4% to 9%
++ Requesting appointments is a specific type of engagement. Provide a phone number or link to an online + appointment + request form. +
++ The Code for America’s Texting + Playbook + reported specific learnings around appointment reminders, completing + document submission, and maintenance reminders. +
+Benchmark: Clients were 79% more + likely + to keep + their appointment after receiving a text reminder.
+You will likely see more completed appointments.
+Benchmark: Clients were 55% more + likely + to complete + an interview after receiving an interview reminder
+You will likely see more completed interviews.
++ Benchmark: Clients were 6% more likely to complete document submission after + receiving a customized list of required documents via text +
+To encourage response, provide a custom list of the needed documents and information about how to submit + them. +
+Benchmark: Text reminders improved case maintenance rates by 21%
+You may see less turnover in your case rates.
+For texting the public
+Effectively reaching your audience and supporting your program’s goals starts with strategically planning out what + text messages can help you achieve and how to approach a thoughtful rollout. +
+ +This set of best practices will help you get an effective texting initiative up and running.
+Start with a singular purpose. Make explicit what you want to achieve.
+Text messaging should be one part of how you communicate with the people you serve, and it is best used to + provoke + action or influence behavior. Therefore, when starting to plan your texting campaign, it’s important to start + with + the end in mind. +
++ To establish clear goals, start by answering the following questions: +
+Use texting to share timely + information, provide critical status updates, or remind someone to do something.
+Examples:
+ + {% set list_item_dos = [ + { + "list_content": "Your application is due on…", + }, + { + "list_content": "We need more information from you to process your application for…", + }, + { + "list_content": "We will be calling you about…at…", + }, + { + "list_content": "We will be calling you about…at…", + }, + ] %} +Texting is not great for long, + detailed instructions or frequent reminders that may be ignored. +
+Examples:
+ {% set list_item_donts = [ + { + "list_content": "Your rights and responsibilities", + }, + { + "list_content": "How to complete your application", + }, + { + "list_content": "Weekly reminder not to share your account information", + }, + ] %} +Your program may already be communicating with your audience through methods like mailed notices and phone calls. + Think of text messages as complementary to your existing communications. Review how well you are currently reaching + your audience. Are there methods or gaps that could be supplemented with a text message? +
++ The Department of Veterans Affairs provides a helpful flow-chart that can help you decide if a text message is needed + for the communication problem you are trying to solve. +
++ Think of your texting campaign as a scientific experiment, and try to articulate what you hope to improve as a + result of implementing text messaging at your agency. For example: +
++ We believe that texting [specific group of people] when [this happens or on this + cadence] to inform/alert them about [subject] will result in [desired action]. +
++ Review your drafted hypothesis with your team to make sure everyone is aligned on your desired goals. A clear and + concise hypothesis can help you decide how to write text message + content + that provokes action. +
++ To help you decide how much to invest in text messaging over time, we highly recommend starting small and testing + your message campaigns to refine based on feedback and evidence. +
++ Starting small has less risk and more flexibility. It can help you gather data and decide if and how to send + more text messages. For example, start by sending one message to a smaller sample of your audience and track their + resulting behavior. +
++ At a minimum, make sure to get feedback on your message content from staff, community-based organizations, and, + ideally, from people who receive the texts themselves to ensure your messages are clear and actionable. +
+Building and operationalizing a texting initiative requires time and effort, and you want to be able to demonstrate + the results of that effort.
++ To understand if your texting program is successful, ask: +
+Help your audience anticipate and welcome your texts.
++ People are wary of texts they’re not expecting to receive. Before you send your first text, consider how you will + gain trust with your audience and address any concerns of spam. Seize specific moments to reinforce what your + audience should expect from your messages. +
+Before you launch, let your audience know you are adding texting as a communication method. People are more likely + to trust your text once they get it if they know that your program is sending texts.
+ +How you currently communicate with your audience will determine the best approaches to announce your new + initiative. Harness channels you’re already using like social media, public websites, mailing inserts, or fliers at + key service locations as well as partnering with community organizations to get the word out about your new texting + program.
+
+ To reinforce legitimacy, include these key messages in your outreach:
+When a person has questions or needs assistance, reaching out for support may be natural for them. Give direction + in your texts so your audience understands what to do with the information you provide. Remember, it’s important to + repeat consistent and standardized language across texts and programs.
+If you're sending one-way notifications, phone carriers allow a single auto-response message that will be + generated if a recipient tries to text a response to your message. Use the auto-response to reaffirm your key + messages around legitimacy and communicate to recipients that texts are coming from an automated system.
++ Your texting program will only be perceived as legitimate if you get the word out in many different ways. It's also + important that agency employees who support text recipients (for example, the call center) are familiar with the + texting program. They can accurately respond to recipient questions about them, so make sure you’re priming your + delivery staff, especially those who interact directly with your intended audience. +
+ +What to know as you plan translated texts.
+ ++ Sending messages in recipients’ preferred language demonstrates respect, makes it more likely that recipients will + understand and take action on the message, and may be required by your agency or program. +
+ ++ To send translated messages using Notify.gov you will need two things: a record of your recipients’ preferred + language and translation/s of the message template. Notify.gov does not provide translation services. +
+ ++ As with messages in English, we strongly recommend user testing the translations with people who speak the language + to ensure the messages are understandable. +
+Understand what is required when texting the public.
++ Providing consent and opting out are key ways for text recipients to express their text communication preferences. + Discuss your obligation to obtain consent with your legal counsel. +
++ If you do need expressed consent, consider including a pre-checked plain language opt-in (i.e. “It’s OK to text + me.”) on + digital forms. Be sure to ask for an up-to-date phone number and include a question about the recipient’s preferred + language for text messages if you expect to translate your text + messages in + languages other than English. +
+ {% set links = [ + { + "p_text": 'The Telephone Consumer + Protection Act (TCPA) (47 USC § 227) is the federal law that impacts how organizations are allowed to + communicate + in bulk with the public via telephone (including text message or SMS).' + }, + { + "p_text": 'The FCC has ruled that Federal and State programs are exempt from the TCPA and can + send + text messages to the public without consent if conducting official business. Without explicit mention in the ruling, + local governments, phone carriers, or any texting intermediaries might require it.' + }, + { + "url_link": + "pdf/tcpa_overview.pdf", + "url_text":"Download and share our overview of the TCPA with your legal counsel", + "is_downloadable": true, + "p_text": 'It provides a baseline + interpretation to aid your legal counsel in getting up to speed with what might be needed for your program or use + case. + For additional questions about the enforcement of the TCPA, you can watch a recorded training on public + benefits texting provided by the FCC.' + }, + ] %} ++ {{item.p_text |safe}} +
++ There is no policy requirement for senders to communicate opt-out options, but including instructions in introductory and/or + auto-response texts on how to opt out and opt back in are effective ways to establish trust with your + audience. +
+ ++ In the US, no matter where texts are coming from, a recipient has the right to revoke consent at any time through + any + reasonable means. For example, a text recipient can always opt out of receiving text messages from a phone number by + responding “STOP”, “QUIT”, or one of several other keywords. +
+ ++ Any subsequent messages sent to that number will be blocked by the phone carrier and not delivered. We recommend + establishing an internal process to obtain and remove numbers that fail multiple times to avoid continuing to send + messages to them. +
++ When putting together the data spreadsheet (or CSV) file containing text message data, follow relevant security + processes for storing and transmitting the file that contains PII. +
++ Sensitive information should be protected and secure. Social Security Numbers, driver’s license numbers, personal + health information, and passwords are all sensitive PII that should not be shared via text message. Consult with + your privacy office to understand what information you can and cannot include in a text message. Different programs + and jurisdictions may have different rules and interpretations. +
++ Consider the risks if someone who is not the intended recipient receives the text message. Establish an internal + process + to keep your phone number list up-to-date. The FCC offers a Reassigned + Numbers Database to identify phone numbers that may have been reassigned since your agency obtained them. +
+Help your audience know what to do with the information you send.
++ When writing a text, focus on the person who is going to receive the message and write it for them. Use plain + language and provide easy to understand next steps. +
++ When deciding what types of messages to draft for your campaign, think through the following questions: +
+For example, getting a person to update their mailing address:
+Evidence + shows that employing + behavioral science is an effective way to increase the + likelihood of a recipient + taking action upon receiving your text. Keep in mind these three foundational principles while drafting your text + message:
+ {% set card_contents = [ + { + "card_heading": "Feeling a sense of ownership can influence behavior.", + "p_text": "Example – Don't miss this chance to get free internet or $30 per month off your internet bill.", + }, + { + "card_heading": "Individuals are strongly influenced by others’ behaviors.", + "p_text": "Example – Thousands of residents already get an + average of $113/month in money for food.", + }, + { + "card_heading": "Individuals perceive losses more powerfully than gains.", + "p_text": "Example – Contact us for potentially unclaimed free + internet services that belong to you.", + }, + ] %} + {% with card_contents=card_contents, text_align='left' %}{% include "components/best-practices/cards.html" %}{% + endwith %} +Below we explain how to:
Effective texts will help your message recipients take the steps needed to secure and keep the benefits and services they depend on. To craft an effective text:
@@ -37,7 +42,7 @@When composing a text message, links to websites or online applications can help your recipient respond quickly.
@@ -64,6 +70,7 @@Texting fraud is ever prevalent and while we can’t eliminate the risk, we can reduce the chances that recipients will @@ -240,5 +248,5 @@ in a text. If you have questions about how to protect your privacy, see statename.gov/privacy.
- + {% endif %} {% endblock %} diff --git a/app/templates/views/roadmap.html b/app/templates/views/roadmap.html index 97f982e12..f9446aad5 100644 --- a/app/templates/views/roadmap.html +++ b/app/templates/views/roadmap.html @@ -33,7 +33,7 @@