> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cfosilvia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SMS RADAR NOTIFICATIONS GUIDE

# SMS Radar Notifications - Complete Guide

## What Is This?

Internal users can now receive **text message alerts** when their radar schedules detect important changes in their portfolio. This works exactly like email notifications, but sends an SMS instead.

***

## How It Works (Simple Flow)

```
1. Internal user creates a radar with SMS enabled ✅
         ↓
2. Radar schedule saved to DynamoDB with smsEnabled: true
         ↓
3. AWS Lambda runs radar on schedule (e.g., daily at 9am)
         ↓
4. Lambda analyzes portfolio using AI
         ↓
5. Lambda checks: "Should I send an alert?"
         ↓
6. If YES → Lambda calls Next.js API to send SMS
         ↓
7. Next.js checks user has SMS enabled & phone number
         ↓
8. Next.js sends SMS via Twilio
         ↓
9. User receives text message on their phone 📱
```

***

## The Three Main Parts

### Part 1: Frontend (React/Next.js)

**What it does**: Shows SMS toggle to internal users, saves their choice

**Files**:

* `components/radar-schedules-sidebar.tsx` - SMS checkbox UI
* `app/radar/hooks.ts` - Manages `smsEnabled` state
* `app/radar/types.ts` - Defines `smsEnabled` in schedule config

**How it works**:

1. User opens Radar page
2. If user is in `internal` table → SMS toggle appears
3. User checks "SMS" when creating radar
4. Frontend saves: `{ smsEnabled: true }` to DynamoDB

### Part 2: AWS Lambda (Python)

**What it does**: Executes radar, decides if alert should be sent, triggers SMS

**File**: `aws/radar/lambdas/processor/handler.py`

**How it works** (step by step):

#### Step 1: Lambda Gets Triggered

```python theme={null}
# Lambda receives event from EventBridge (scheduled) or API Gateway (manual)
event = {
    "scheduleId": "schedule_abc123",
    "userId": "user_xyz789",
    "radarType": "custom"
}
```

#### Step 2: Lambda Fetches Radar Schedule

```python theme={null}
# Get schedule from DynamoDB
schedule = schedules_table.get_item(
    Key={"PK": f"USER#{user_id}", "SK": f"SCHEDULE#{schedule_id}"}
)

# Example schedule data:
{
    "promptTemplate": "Alert me when my tech allocation exceeds 40%",
    "scheduleConfig": {
        "frequency": "DAILY",
        "timeUtc": "09:00",
        "emailEnabled": true,
        "smsEnabled": true  # ← This is what we check!
    }
}
```

#### Step 3: Lambda Runs AI Analysis

```python theme={null}
# Call internal chat API to analyze portfolio
response = requests.post(
    f"{INTERNAL_API_URL}/chat",
    json={
        "userId": user_id,
        "prompt": schedule["promptTemplate"],
        "radarMode": true
    }
)

# AI returns analysis:
{
    "summary": "Your tech allocation is now 45%, exceeding your 40% threshold.",
    "structuredOutput": {
        "sendAlert": true,  # ← Should we notify user?
        "triggerMet": true,
        "confidence": "high"
    }
}
```

#### Step 4: Lambda Checks If SMS Should Be Sent

```python theme={null}
# Line ~885 in handler.py

# Get flags from schedule config
email_enabled = schedule.get("scheduleConfig", {}).get("emailEnabled", True)
sms_enabled = schedule.get("scheduleConfig", {}).get("smsEnabled", False)

# Get AI's decision
structured_output = result_data.get("structuredOutput", None)
send_alert = structured_output.get("sendAlert", True) if structured_output else True

# Decision logic:
if sms_enabled and send_alert:
    # YES - Send SMS!
    print("✅ SMS enabled AND trigger met → Sending SMS")
elif sms_enabled and not send_alert:
    # NO - Radar ran but condition not met
    print("⏭️ SMS enabled BUT trigger not met → Skipping SMS")
else:
    # NO - SMS not enabled
    print("❌ SMS not enabled → Skipping SMS")
```

#### Step 5: Lambda Sends SMS (If Conditions Met)

```python theme={null}
# Line ~920 in handler.py

if sms_enabled and send_alert:
    # Call the SMS sending function
    sms_status = send_completion_sms(
        user_id=user_id,
        schedule_id=schedule_id,
        radar_id=radar_id,
        summary=result_data.get("summary", "Analysis completed"),
        prompt=schedule["promptTemplate"],
        execution_id=execution_id
    )
    
    # sms_status returns:
    # {
    #     "status": "SENT",
    #     "sentAt": "2025-11-05T10:30:00Z",
    #     "twilioMessageSid": "SM_abc123"
    # }
```

#### Step 6: Lambda Calls Next.js API

```python theme={null}
# Inside send_completion_sms() function (Line ~90)

def send_completion_sms(user_id, schedule_id, radar_id, summary, prompt, execution_id):
    """
    This function makes an HTTP request to your Next.js app
    """
    
    # 1. Build the API endpoint URL
    sms_endpoint = f"{NEXTJS_URL}/api/sms/radar-complete"
    # Example: "https://cfosilvia.com/api/sms/radar-complete"
    
    # 2. Prepare the data to send
    payload = {
        "userId": user_id,           # "user_xyz789"
        "scheduleId": schedule_id,   # "schedule_abc123"
        "radarId": radar_id,         # "radar_def456"
        "summary": summary,          # "Your tech allocation is now 45%..."
        "prompt": prompt             # "Alert me when tech exceeds 40%"
    }
    
    # 3. Make HTTP POST request to Next.js
    response = requests.post(
        sms_endpoint,
        json=payload,
        timeout=10  # Wait max 10 seconds
    )
    
    # 4. Check if it worked
    if response.status_code == 200 and response.json().get("success"):
        print("✅ SMS sent successfully!")
        return {
            "status": "SENT",
            "sentAt": datetime.now().isoformat(),
            "twilioMessageSid": response.json().get("twilioMessageSid")
        }
    else:
        print("❌ SMS failed!")
        return {
            "status": "FAILED",
            "error": f"HTTP {response.status_code}"
        }
```

#### Step 7: Lambda Saves SMS Status to DynamoDB

```python theme={null}
# Line ~960 in handler.py

# Update the radar result with SMS status
result_item["smsStatus"] = sms_status.get("status")        # "SENT"
result_item["smsSentAt"] = sms_status.get("sentAt")       # "2025-11-05T10:30:00Z"
result_item["twilioMessageSid"] = sms_status.get("twilioMessageSid")  # "SM_abc123"

# Save to DynamoDB
results_table.put_item(Item=result_item)

print("✅ Result saved with SMS status")
```

### Part 3: Next.js API (TypeScript)

**What it does**: Receives request from Lambda, sends SMS via Twilio

**File**: `app/api/sms/radar-complete/route.ts`

**How it works**:

#### Step 1: Receive Request from Lambda

```typescript theme={null}
export async function POST(request: NextRequest) {
  // Parse the data Lambda sent us
  const { userId, scheduleId, radarId, summary, prompt } = await request.json()
  
  // Example:
  // userId: "user_xyz789"
  // summary: "Your tech allocation is now 45%, exceeding your 40% threshold."
  // prompt: "Alert me when my tech allocation exceeds 40%"
}
```

#### Step 2: Get User's Phone Number

```typescript theme={null}
// Query Supabase for user's phone number and SMS preferences
const { data: user } = await supabase
  .from('all_users')
  .select('phone_number, sms_opt_in')
  .eq('clerk_id', userId)
  .single()

// Example result:
// {
//   phone_number: "16082159565",
//   sms_opt_in: true
// }
```

#### Step 3: Validate User Can Receive SMS

```typescript theme={null}
// Check 1: Does user have SMS enabled?
if (!user.sms_opt_in) {
  return NextResponse.json({
    success: false,
    reason: 'SMS_NOT_ENABLED'
  })
}

// Check 2: Does user have a phone number?
if (!user.phone_number) {
  return NextResponse.json({
    success: false,
    reason: 'NO_PHONE_NUMBER'
  })
}
```

#### Step 4: Format the SMS Message

```typescript theme={null}
// Keep it short and sweet (under 300 characters)
const promptPreview = prompt.substring(0, 50) + '...'
const summaryPreview = summary.substring(0, 120) + '...'

const message = `🔔 Radar Alert: ${promptPreview}

${summaryPreview}

View details: cfosilvia.com/radar`

// Example output:
// "🔔 Radar Alert: Alert me when my tech allocation exce...
//
// Your tech allocation is now 45%, exceeding your 40% threshold. Consider rebalancing to reduce risk.
//
// View details: cfosilvia.com/radar"
```

#### Step 5: Send SMS via Twilio

```typescript theme={null}
// Call Twilio utility function (already exists in your codebase)
const twilioMessageSid = await sendLongSMS(user.phone_number, message)

// sendLongSMS() does:
// 1. Connects to Twilio API
// 2. Sends SMS to user's phone
// 3. Returns Twilio message ID (e.g., "SM_abc123")
```

#### Step 6: Log SMS in Database

```typescript theme={null}
// Save to silvia_sms_logs table for tracking
await supabase.from('silvia_sms_logs').insert({
  clerk_id: userId,
  phone_number: user.phone_number,
  direction: 'outbound',
  message_content: message,
  twilio_message_sid: twilioMessageSid,
  user_context: {
    type: 'radar_alert',
    radarId,
    scheduleId
  }
})
```

#### Step 7: Return Success to Lambda

```typescript theme={null}
return NextResponse.json({
  success: true,
  twilioMessageSid: twilioMessageSid
})

// Lambda receives this response and knows SMS was sent successfully
```

***

## Complete Example Flow

Let's walk through a real example:

### Setup

* User: John (internal user)
* Radar: "Alert me when my tech allocation exceeds 40%"
* Schedule: Daily at 9:00 AM
* SMS: Enabled ✅

### Day 1: 9:00 AM - Radar Runs

**1. EventBridge triggers Lambda**

```
Event: "It's 9:00 AM, run John's radar"
```

**2. Lambda fetches schedule**

```python theme={null}
schedule = {
    "promptTemplate": "Alert me when my tech allocation exceeds 40%",
    "scheduleConfig": {
        "smsEnabled": True  # ← SMS is on!
    }
}
```

**3. Lambda analyzes portfolio**

```python theme={null}
# Calls AI to check John's portfolio
result = {
    "summary": "Your tech allocation is 35%. No action needed.",
    "structuredOutput": {
        "sendAlert": False  # ← Condition NOT met (35% < 40%)
    }
}
```

**4. Lambda checks if SMS should be sent**

```python theme={null}
sms_enabled = True   # ✅ SMS is enabled
send_alert = False   # ❌ Trigger not met (35% < 40%)

# Decision: Don't send SMS
if sms_enabled and send_alert:  # False AND False = False
    send_sms()  # ← This doesn't run
```

**5. Lambda saves result**

```python theme={null}
result_item = {
    "smsStatus": "SKIPPED_TRIGGER_NOT_MET",
    "summary": "Your tech allocation is 35%. No action needed."
}
```

**Result**: No SMS sent (condition not met) ✅

***

### Day 2: 9:00 AM - Radar Runs Again

**1. EventBridge triggers Lambda** (same as Day 1)

**2. Lambda fetches schedule** (same as Day 1)

**3. Lambda analyzes portfolio**

```python theme={null}
# John bought more tech stocks yesterday
result = {
    "summary": "Your tech allocation is now 45%, exceeding your 40% threshold.",
    "structuredOutput": {
        "sendAlert": True  # ← Condition MET! (45% > 40%)
    }
}
```

**4. Lambda checks if SMS should be sent**

```python theme={null}
sms_enabled = True   # ✅ SMS is enabled
send_alert = True    # ✅ Trigger met (45% > 40%)

# Decision: Send SMS!
if sms_enabled and send_alert:  # True AND True = True
    send_sms()  # ← This RUNS!
```

**5. Lambda calls `send_completion_sms()`**

```python theme={null}
sms_status = send_completion_sms(
    user_id="user_john123",
    summary="Your tech allocation is now 45%, exceeding your 40% threshold.",
    prompt="Alert me when my tech allocation exceeds 40%"
)
```

**6. `send_completion_sms()` calls Next.js API**

```python theme={null}
# Makes HTTP request to Next.js
requests.post(
    "https://cfosilvia.com/api/sms/radar-complete",
    json={
        "userId": "user_john123",
        "summary": "Your tech allocation is now 45%...",
        "prompt": "Alert me when my tech allocation exceeds 40%"
    }
)
```

**7. Next.js API receives request**

```typescript theme={null}
// Gets user's phone number
user = {
    phone_number: "16082159565",
    sms_opt_in: true
}

// Formats message
message = "🔔 Radar Alert: Alert me when my tech allocation exce...

Your tech allocation is now 45%, exceeding your 40% threshold.

View details: cfosilvia.com/radar"

// Sends via Twilio
twilioMessageSid = await sendLongSMS("16082159565", message)
// Returns: "SM_abc123"
```

**8. Next.js returns success to Lambda**

```typescript theme={null}
return { success: true, twilioMessageSid: "SM_abc123" }
```

**9. Lambda saves result**

```python theme={null}
result_item = {
    "smsStatus": "SENT",
    "smsSentAt": "2025-11-05T09:00:15Z",
    "twilioMessageSid": "SM_abc123"
}
```

**10. John receives text message** 📱

```
🔔 Radar Alert: Alert me when my tech allocation exce...

Your tech allocation is now 45%, exceeding your 40% threshold.

View details: cfosilvia.com/radar
```

**Result**: SMS sent successfully! ✅

***

## Why This Design?

### Why Lambda → Next.js → Twilio?

**Why not Lambda → Twilio directly?**

1. **Separation of concerns**: Lambda handles radar logic, Next.js handles user communication
2. **Reuse existing code**: Next.js already has Twilio integration, SMS logging, user validation
3. **Security**: Twilio credentials stay in Next.js environment variables
4. **Consistency**: All SMS (chat, radar, etc.) go through the same Next.js API
5. **Easier debugging**: Can test Next.js API independently of Lambda

### Why Check `sms_opt_in` in Next.js?

**Why not check in Lambda?**

* Lambda doesn't have direct Supabase access (uses DynamoDB)
* Next.js already has user data access patterns
* Keeps user preference logic in one place
* Easier to add more SMS validation rules later

***

## Key Files Summary

| File | Purpose | Language |
| - | - | - |
| `components/radar-schedules-sidebar.tsx` | SMS toggle UI | TypeScript |
| `app/radar/hooks.ts` | Manage `smsEnabled` state | TypeScript |
| `app/radar/types.ts` | Type definition for `smsEnabled` | TypeScript |
| `hooks/queries/use-is-internal.ts` | Check if user is internal | TypeScript |
| `app/api/user/is-internal/route.ts` | API to check internal status | TypeScript |
| `app/api/sms/radar-complete/route.ts` | **Send SMS via Twilio** | TypeScript |
| `aws/radar/lambdas/processor/handler.py` | **Execute radar & trigger SMS** | Python |
| `lib/twilio.ts` | Twilio SMS utilities | TypeScript |

***

## Testing Checklist

### 1. Add yourself to internal table

```sql theme={null}
INSERT INTO internal (clerk_id) VALUES ('your_clerk_id_here');
```

### 2. Create a test radar

* Go to Radar page
* Click "New Alert"
* Enter prompt: "Alert me when my net worth changes"
* Check "SMS" toggle ✅
* Save

### 3. Trigger manually

* Click "Run Now" on your radar
* Wait 10-30 seconds

### 4. Check Lambda logs (CloudWatch)

```
Search for: "Sending radar completion SMS"
Should see: "Radar SMS sent successfully"
```

### 5. Check your phone

* You should receive a text message 📱

### 6. Check database

```sql theme={null}
-- Check SMS was logged
SELECT * FROM silvia_sms_logs 
WHERE clerk_id = 'your_clerk_id_here' 
ORDER BY created_at DESC 
LIMIT 5;
```

***

## Troubleshooting

### "SMS toggle doesn't appear"

→ You're not in the `internal` table

### "SMS status is SKIPPED"

→ Check `smsReason` in DynamoDB:

* `SMS_NOT_ENABLED`: User has `sms_opt_in = false`
* `NO_PHONE_NUMBER`: User has no phone number
* `SKIPPED_TRIGGER_NOT_MET`: Radar ran but condition not met

### "SMS status is FAILED"

→ Check Next.js logs for errors:

* Twilio API error?
* Phone number format invalid?
* Twilio account balance?

### "Lambda timeout"

→ Check CloudWatch logs:

* Is Next.js API responding?
* Network connectivity issue?

***

## Cost

**Per SMS**: \~\$0.008 (Twilio fee)

**Monthly estimate** (10 internal users, 5 radars each):

* 10 users × 5 radars × 30 days = 1,500 SMS/month
* 1,500 × $0.008 = **$12/month\*\*

***

## Summary

**What you built**:

1. ✅ Frontend: SMS toggle for internal users
2. ✅ Lambda: Check `smsEnabled` flag and trigger SMS
3. ✅ Next.js API: Send SMS via Twilio
4. ✅ Database: Log all SMS activity

**How it works**:

1. User enables SMS on radar
2. Lambda runs radar on schedule
3. If trigger met → Lambda calls Next.js API
4. Next.js sends SMS via Twilio
5. User receives text message

**Key insight**: Lambda doesn't send SMS directly. It calls your Next.js API, which handles all the SMS logic (validation, Twilio, logging). This keeps your code clean and reusable.

***

**Status**: ✅ Ready to deploy\
**Next step**: Deploy Lambda changes to AWS, test with internal user


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.