-
Notifications
You must be signed in to change notification settings - Fork 2
Add notifications-sms module to support SMS notifications #256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
jrpbc
wants to merge
12
commits into
main
Choose a base branch
from
johan/notifications-sms
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
4ed93c4
Add notifications-sms module to support SMS notifications
jrpbc 8a0e374
Allow github-action access to create/update SMS-Voice and CloudFormat…
jrpbc cfe58e3
Add to SNS Notification Module SMS Phone Number Pool associated with …
jrpbc 63421bb
Remove unused variables
jrpbc 004cbed
Make the description of the sms_simulator_phone_number_id variable, c…
jrpbc 9cbf1ec
Fix terraform formatting
jrpbc 346f42b
Remove SMS Simulator phone number as input variable for the SMS Notif…
jrpbc 7b19e10
Fix CloudFormation destroy dependency ordering
jrpbc 32e10ba
Create SMS notifications feature documentation
jrpbc 6c2f7c0
Create SMS notifications implementation ADR
jrpbc 890c33d
Refactor into modules notifications-phone-pool and notifications-sms
jrpbc 599b06a
Refactor into modules notifications-phone-pool and notifications-sms
jrpbc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import logging | ||
| import os | ||
| import boto3 | ||
| import json | ||
| from botocore.exceptions import ClientError | ||
|
|
||
| logging.basicConfig() | ||
| logger = logging.getLogger() | ||
| logger.setLevel(logging.INFO) | ||
|
|
||
| def send_sms(phone_number, message, message_type="TRANSACTIONAL"): | ||
| """ | ||
| Send SMS via AWS End User Messaging (PinpointSMSVoiceV2) | ||
| """ | ||
| try: | ||
| logger.info("Initializing AWS Pinpoint SMS Voice V2 client") | ||
| client = boto3.client("pinpoint-sms-voice-v2") | ||
| phone_pool_id = os.environ.get("AWS_SMS_PHONE_POOL_ID") | ||
| configuration_set = os.environ.get("AWS_SMS_CONFIGURATION_SET_NAME") | ||
| logger.info("Sending SMS Using Configuration Set: %s", configuration_set) | ||
| logger.info("Sending SMS Using Phone Pool ID: %s", phone_pool_id) | ||
|
|
||
| params = { | ||
| "DestinationPhoneNumber": phone_number, | ||
| "OriginationIdentity": phone_pool_id, | ||
| "MessageBody": message, | ||
| "MessageType": message_type | ||
| } | ||
|
|
||
| # Add configuration set for tracking | ||
| if configuration_set: | ||
| params["ConfigurationSetName"] = configuration_set | ||
|
|
||
| # Add context for tracking (optional) | ||
| params["Context"] = { | ||
| "ApplicationName": "template-app", | ||
| "Environment": "dev" | ||
| } | ||
|
|
||
| if check_opt_out_status(phone_number).get("opted_out"): | ||
| logger.warning("Phone number %s has opted out of SMS messages. Aborting send.", phone_number) | ||
| return { | ||
| "success": False, | ||
| "error": f"Phone number {phone_number} has opted out of SMS messages." | ||
| } | ||
|
|
||
| response = client.send_text_message(**params) | ||
|
|
||
| return { | ||
| "success": True, | ||
| "message_id": response.get("MessageId"), | ||
| "response": response | ||
| } | ||
|
|
||
| except ClientError as e: | ||
| error_code = e.response["Error"]["Code"] | ||
| error_message = e.response["Error"]["Message"] | ||
| logger.error(f"ClientError: {error_code} - {error_message}") | ||
|
|
||
| return { | ||
| "success": False, | ||
| "error": error_message, | ||
| "error_code": error_code, | ||
| "details": str(e) | ||
| } | ||
| except Exception as e: | ||
| return { | ||
| "success": False, | ||
| "error": str(e) | ||
| } | ||
|
|
||
| def check_opt_out_status(phone_number): | ||
| """ | ||
| Check if a phone number is opted out | ||
| """ | ||
| try: | ||
| client = boto3.client("pinpoint-sms-voice-v2") | ||
|
|
||
| response = client.describe_opted_out_numbers( | ||
| OptOutListName="default", | ||
| OptedOutNumbers=[phone_number] | ||
| ) | ||
|
|
||
| opted_out_numbers = response.get("OptedOutNumbers", []) | ||
| is_opted_out = any(num["OptedOutNumber"] == phone_number for num in opted_out_numbers) | ||
|
|
||
| return {"opted_out": is_opted_out} | ||
|
|
||
| except Exception as e: | ||
| return {"error": str(e)} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>SMS Test</title> | ||
| </head> | ||
| <body> | ||
| <h1>Send Test SMS</h1> | ||
| <form method="post"> | ||
| <div> | ||
| <label for="phone_number">Receiver Phone Number (E.164 format: +1234567890):</label><br> | ||
| <input type="tel" id="phone_number" name="phone_number" | ||
| pattern="^\+[1-9]\d{1,14}$" | ||
| placeholder="+1234567890" | ||
| required><br> | ||
| <small>Must include country code (e.g., +1 for US)</small><br> | ||
| <small><strong>If using simulator phone numbers for testing:</strong><br> | ||
| • +14254147755 (Success Text testing)<br> | ||
| • +14254147167 (Text Blocked testing)</small> | ||
| </div> | ||
| <br> | ||
| <button type="submit">Send Test SMS</button> | ||
| </form> | ||
| </body> | ||
| </html> | ||
113 changes: 113 additions & 0 deletions
113
docs/decisions/infra/2026-02-19-sms-notifications-implementation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| # ADR: Enable SMS Notifications in Strata AWS Infrastructure Template via AWS End User Messaging Phone Number Pool | ||
|
|
||
| - **Status:** Proposed (Draft) | ||
| - **Date:** 2025-01-28 | ||
| - **Author:** Johan Robles | ||
| - **Related Ticket:** #976 | ||
|
|
||
| --- | ||
|
|
||
| ## Context and Problem Statement | ||
|
|
||
| The Nava Strata AWS Infrastructure template currently supports Email notifications. As part of expanding multi-channel communication capabilities, the team is introducing SMS notifications into the Strata offering. | ||
|
|
||
| At the infrastructure layer, this feature leverages AWS End User Messaging (SMS), which introduces several operational and provisioning considerations that impact how SMS is enabled within the Nava Strata Infrastructure Template: | ||
| - Arbitrary phone numbers cannot be used. Phone number registration and approval is a manual AWS-managed external process that may take approximately 1–15 days depending on region and use case. | ||
| - AWS recommends managing approved originators through a Phone Number Pool. Benefits for this solution: | ||
| - Automatic failover if an originator fails - Originator phone numbers are controlled and managed by external phone carriers (not AWS) | ||
| - Rotation of numbers without application code changes | ||
| - Ability to temporarily include simulator numbers for development | ||
| - The current Terraform AWS Provider has limited support for SMS Voice v2 resources: | ||
| - Event Destinations (e.g., `TEXT_DELIVERED`, `TEXT_BLOCKED`, `TEXT_FAILURE`) cannot be fully configured and linked to a Configuration Set using Terraform. Carrier-level delivery events are necessary to understand actual delivery outcomes (which differ from API-level success responses). | ||
| - Phone Number Pools cannot be provisioned using the Terraform AWS Provider. | ||
| - Because of these provider limitations, certain resources must be provisioned using AWS CloudFormation, invoked from Terraform via `aws_cloudformation_stack`. | ||
|
|
||
| ## Decision Outcome | ||
|
|
||
| Implement SMS notification enablement using **Infrastructure-Provisioned Phone Number Pool via CloudFormation definition within Terraform (Option 4)**, including: | ||
|
|
||
| 1. A new infrastructure module: `notifications-sms` | ||
| 2. CloudFormation-managed SMS resources (via Terraform) | ||
| 3. A Phone Number Pool module `notifications-phone-pool` with associated phone numbers | ||
| 4. Carrier-level delivery event logging to CloudWatch | ||
| 5. IAM policies scoped to the Phone Pool ARN (least privilege) | ||
| 6. Conditional VPC Interface Endpoint for `sms-voice` | ||
| 7. Standardized outputs for application integration | ||
|
|
||
| ## Considered Options | ||
|
|
||
| ### Option 1 — Basic SMS Enablement | ||
|
|
||
| Provision: | ||
| - VPC Interface Endpoint | ||
| - Configuration Set | ||
| - IAM permission for `sms-voice:SendTextMessage` | ||
|
|
||
| **Pros** | ||
| - Simplest implementation | ||
| - Fully supported via Terraform AWS Provider | ||
|
|
||
| **Cons** | ||
| - Application teams manage phone number resources | ||
| - Requires broad IAM Access policy permissions | ||
| - No carrier-level delivery visibility | ||
|
|
||
| ### Option 2 — Add Carrier-Level Delivery Monitoring | ||
|
|
||
| Builds on Option 1 and adds: | ||
| - Event destinations for: | ||
| - `TEXT_DELIVERED` | ||
| - `TEXT_BLOCKED` | ||
| - `TEXT_FAILURE` | ||
| - Other asynchronous carrier responses | ||
|
|
||
| **Pros** | ||
| - Delivery visibility | ||
| - Enables reliability improvements (e.g., the possibility of adding message retry mechanism) | ||
|
|
||
| **Cons** | ||
| - Requires CloudFormation integration which increase implementation complexity | ||
|
|
||
| ### Option 3 — Infrastructure-Provisioned Single Phone Number | ||
|
|
||
| Builds on Option 2 and provisions a single originator number. | ||
|
|
||
| **Pros** | ||
| - App teams do not manage phone numbers (just the external registration process) | ||
| - Application IAM Access Policy can be restricted to one number which improves security posture | ||
|
|
||
| **Cons** | ||
| - Any testing is blocked until originator phone number approval | ||
| - Originator phone number rotation requires Terraform changes | ||
| - Single number increases operational risk | ||
|
|
||
| ### Option 4 — Infrastructure-Provisioned Phone Number Pool (Selected) | ||
|
|
||
| Builds on Option 2 and provisions: | ||
|
|
||
| - Phone Number Pool | ||
| - Associated phone number (When phone number registration is approved) | ||
| - Optional simulator phone number is a provisioned for development purpose | ||
|
|
||
| **Pros** | ||
| - Aligns with AWS best practices | ||
| - Supports number rotation without code changes | ||
| - Application IAM Access Policy scoped to pool ARN (least privilege) | ||
| - Simulator Phone Number support for development - no need to wait for originator phone number approval for basic testing. | ||
| - Reduced operational risk - Multiple phone numbers can be added to the pool | ||
|
|
||
| **Cons** | ||
| - Higher infrastructure complexity | ||
| - Requires CloudFormation integration | ||
|
|
||
| ## Rationale | ||
|
|
||
| Option 4 provides: | ||
|
|
||
| - Strong security posture through least-privilege IAM | ||
| - Improved operational resilience via number pooling | ||
| - Carrier-level delivery observability | ||
| - Development/testing flexibility via simulator phone number | ||
| - Alignment with AWS best practices | ||
|
|
||
| It balances reliability, security, and observability while managing Terraform provider limitations. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can make this a select element populated by the rendering endpoint, with only the predefined simulator numbers by default.
(longer term we should probably better lock down the email testing endpoint as well, we don't really want to be a vehicle to spam people)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restricting this to the simulator numbers here will prevent from testing approved phone numbers. I added an extra message to this form, specifying the phone numbers that can be used for testing if using simulator phone number as originator.