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

# Email & SMS Setup Guide

> Version: 1.1.0 Last Updated: 2026-03-11

**Version:** 1.1.0\
**Last Updated:** 2026-03-11

This guide documents how to configure email and SMS notification services for the Encore Health OS Platform.

***

## Table of Contents

1. [Overview](#overview)
2. [Email Service (Entra ID & Gmail)](#email-service-entra-id--gmail)
3. [SMS Service (Twilio)](#sms-service-twilio)
4. [Template Configuration](#template-configuration)
5. [Rate Limiting & Quotas](#rate-limiting--quotas)
6. [Cost Optimization](#cost-optimization)
7. [Error Handling](#error-handling)
8. [Testing](#testing)
9. [Troubleshooting](#troubleshooting)

***

## Overview

Encore Health OS uses external services for email and SMS delivery:

* **Email:** Microsoft Entra ID (Graph API) or Gmail (Google Workspace) — org-level sending. Configure per organization in HR Settings and Settings → Integrations.
* **SMS:** Twilio (primary provider)

Email is sent via the shared email-provider abstraction used by:

* `send-email-notification` - Single notification delivery
* `send-pending-notifications` - Batch processing
* `hr-portal-invite`, `hr-reference-request`, `hr-send-communication`, `hr-test-email` - HR recruitment
* `automation-executor` - Workflow send-email actions
* **Signature flows:** `send-signature-request`, `send-signature-reminder`, `auto-signature-reminders` use the shared email-provider (Entra/Gmail) per organization.

***

## Email Service (Entra ID & Gmail)

Org-level email supports two providers. Choose one per organization in **HR → Settings → Email Provider** (and optionally set a fallback).

### Microsoft Entra ID (recommended)

1. Configure the Entra ID integration in **Settings → Integrations → Microsoft Entra ID** (tenant, app registration, admin consent with `Mail.Send`).
2. Set the sender email in the same page (or in `pf_organizations.settings.entra_sender_email`).
3. Set **ENTRA\_CLIENT\_SECRET** in Supabase secrets (client secret for the app registration).
4. In **HR → Settings → Email**, select **Microsoft Entra ID** and optionally set **From Email Address** / **From Name**.

If Entra is selected but fails (e.g. token issue), the system can fall back to Gmail when Gmail is configured for the org.

### Gmail (Google Workspace)

1. **Google Cloud:** Create a project, enable Gmail API, create a service account.
2. **Domain-wide delegation:** In Google Workspace Admin, grant the service account domain-wide delegation for scope `https://www.googleapis.com/auth/gmail.send`.
3. **Supabase secret:** Set the service account JSON as a secret:
   ```bash theme={null}
   supabase secrets set GMAIL_SERVICE_ACCOUNT_JSON='{"type":"service_account","project_id":"...","private_key_id":"...","private_key":"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n","client_email":"...@....iam.gserviceaccount.com",...}'
   ```
4. **Per-organization sender:** In **Settings → Integrations → Google Workspace (Gmail)**, set **Sender email address** to the Gmail/Workspace user to impersonate (e.g. `recruiting@yourcompany.com`). This is stored in `pf_organizations.settings.gmail_sender_email`.
5. In **HR → Settings → Email**, select **Gmail (Google Workspace)** and optionally set **From Email Address** / **From Name**.

**Required secret:** `GMAIL_SERVICE_ACCOUNT_JSON` (full JSON key for the service account).

**Edge functions:** All email senders use `supabase/functions/_shared/email-provider.ts`; no separate provider-specific integration is required beyond Entra/Gmail configuration.

***

## SMS Service (Twilio)

### 1. Create Twilio Account

1. Go to [Twilio](https://www.twilio.com)
2. Sign up for account
3. Verify phone number

### 2. Get Credentials

**Account SID:**

1. Go to **Console** → **Account** → **API Keys & Tokens**
2. Copy **Account SID** (starts with `AC`)

**Auth Token:**

1. Same page as Account SID
2. Click **Show** next to Auth Token
3. Copy Auth Token

**Phone Number:**

1. Go to **Phone Numbers** → **Manage** → **Active Numbers**
2. Purchase phone number (or use trial number)
3. Copy phone number (format: `+1234567890`)

### 3. Set Supabase Secrets

```bash theme={null}
# Set Twilio credentials
supabase secrets set TWILIO_ACCOUNT_SID=ACxxxxx
supabase secrets set TWILIO_AUTH_TOKEN=xxxxx
supabase secrets set TWILIO_PHONE_NUMBER=+1234567890

# Verify secrets are set
supabase secrets list
```

### 4. Configure Edge Function

**Edge Function:** `send-sms-notification`

**Required Secrets:**

* `TWILIO_ACCOUNT_SID`
* `TWILIO_AUTH_TOKEN`
* `TWILIO_PHONE_NUMBER`

**Function Location:** `supabase/functions/send-sms-notification/index.ts`

***

## Template Configuration

### Email Templates

**Template Variables:**

* Use `{{variable_name}}` syntax
* Variables replaced at send time
* Case-sensitive

**Example Template:**

```
Subject: Leave Request {{action}}

Hello {{employee_name}},

Your leave request has been {{action}}.

Details:
- Start Date: {{start_date}}
- End Date: {{end_date}}
- Days: {{days}}

Thank you,
HR Team
```

**Usage in Code:**

```typescript theme={null}
await sendEmailNotification({
  recipientEmail: 'user@example.com',
  subject: 'Leave Request {{action}}',
  body: 'Your request has been {{action}}.',
  variables: {
    action: 'approved',
    employee_name: 'John Doe',
    start_date: '2025-01-15',
    end_date: '2025-01-20',
    days: '5'
  }
});
```

### SMS Templates

**Message Limits:**

* Single SMS: 160 characters
* Messages > 160 characters are truncated
* Use concise templates

**Example Template:**

```
{{action}}: Your leave request for {{start_date}} has been {{status}}. Days: {{days}}
```

**Usage in Code:**

```typescript theme={null}
await sendSMSNotification({
  phoneNumber: '+1234567890',
  message: '{{action}}: Your request has been {{status}}.',
  variables: {
    action: 'Leave Request',
    status: 'approved',
    start_date: '2025-01-15',
    days: '5'
  }
});
```

***

## Rate Limiting & Quotas

### Email (Entra / Gmail) Limits

**Microsoft Graph (Entra):** Throttling and quotas apply per tenant; see Microsoft documentation.

**Gmail API:** Quotas and sending limits apply per Google Cloud project and per user; see Google documentation.

### Twilio Limits

**Trial Account:**

* Limited to verified phone numbers
* SMS only to verified numbers

**Paid Account:**

* No daily/monthly limits
* Pay per SMS sent

**Rate Limits:**

* No explicit rate limit
* Throttling may occur at very high volumes

### Platform Limits

**Encore Health OS Rate Limiting (Planned):**

* Email: Max 100 emails/hour per organization
* SMS: Max 50 SMS/hour per organization
* Enforced in edge functions

**Current Status:** Rate limiting not yet implemented

***

## Cost Optimization

### Email Costs (Entra / Gmail)

**Entra:** Included with Microsoft 365 licensing; Graph API usage subject to tenant limits.

**Gmail:** Subject to Google Workspace and Gmail API quotas; no per-message fee for typical org usage.

**Optimization Tips:**

1. Batch notifications when possible
2. Use `send-pending-notifications` for batch processing
3. Monitor email volume per organization
4. Consider email digests for non-urgent notifications

### SMS Costs (Twilio)

**Pricing:**

* US/Canada: \~\$0.0075 per SMS
* International: Varies by country
* MMS: Higher cost

**Optimization Tips:**

1. Use SMS only for urgent notifications
2. Prefer email for non-urgent communications
3. Batch SMS when possible (future feature)
4. Monitor SMS volume per organization

### Cost Monitoring

**Email providers (Entra/Gmail):**

* Monitor provider dashboards for delivery volume, throttling, and failures
* Configure provider-side alerts/notifications for quota and auth issues

**Twilio:**

* Console shows usage and costs
* Set up spending limits

***

## Error Handling

### Email Errors

**Entra (Graph API):** 4xx/5xx from Graph; check tenant, app permissions, and sender identity.

**Gmail API:** 4xx/5xx from Gmail; check service account, domain-wide delegation, and sender email.

**Error Handling:** The shared `email-provider` returns `EmailSendResult` with `success` and `errorMessage`; callers update notification status and log accordingly.

### SMS Errors

**Common Error Codes:**

* `400` - Invalid phone number
* `403` - Account suspended or insufficient credits
* `500` - Twilio service error

**Error Handling in Function:**

```typescript theme={null}
try {
  const response = await fetch(twilioUrl, { ... });
  if (!response.ok) {
    const errorData = await response.json();
    throw new Error(`Twilio API error: ${errorData.message}`);
  }
  return { success: true, messageSid: data.sid };
} catch (error) {
  // Log error, update notification status
  return { success: false, error: error.message };
}
```

### Retry Logic

**Current Implementation:**

* No automatic retries
* Failed notifications marked as `failed` in database
* Manual retry via `send-pending-notifications`

**Future Enhancement:**

* Automatic retry with exponential backoff
* Max 3 retries
* Alert on persistent failures

***

## Testing

### Test Email Delivery

**Development:**

```typescript theme={null}
// Test email function
const response = await fetch(
  'http://localhost:54321/functions/v1/send-email-notification',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      notificationId: 'test-uuid',
      recipientEmail: 'test@example.com',
      subject: 'Test Email',
      body: 'This is a test email.',
    }),
  }
);
```

**Production:**

* Send to verified email addresses
* Check Supabase function logs and provider dashboards (Entra/Gmail) for delivery status

### Test SMS Delivery

**Development:**

```typescript theme={null}
// Test SMS function
const response = await fetch(
  'http://localhost:54321/functions/v1/send-sms-notification',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      notificationId: 'test-uuid',
      phoneNumber: '+1234567890',  // Verified number in trial
      message: 'Test SMS',
    }),
  }
);
```

**Production:**

* Use Twilio's test credentials
* Send to verified phone numbers (trial)
* Check Twilio console for delivery status

### Test Templates

**Test Template Variables:**

```typescript theme={null}
const testVariables = {
  name: 'John Doe',
  action: 'approved',
  date: '2025-01-07',
  amount: '100.00',
};

// Verify all variables are replaced
const result = template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
  return testVariables[key] || match;
});
```

***

## Troubleshooting

### Issue: Email Not Sending

**Symptoms:**

* No email received
* Function returns success but no email

**Solutions:**

1. **Entra:** Verify ENTRA\_CLIENT\_SECRET and org Entra config (tenant, app, sender email). Check Graph consent includes Mail.Send.
2. **Gmail:** Verify GMAIL\_SERVICE\_ACCOUNT\_JSON and org `gmail_sender_email` in Settings → Integrations → Gmail. Confirm domain-wide delegation for gmail.send.
3. Verify recipient email address is valid.
4. Check spam folder.
5. Review function logs in Supabase dashboard.

### Issue: SMS Not Sending

**Symptoms:**

* No SMS received
* Function returns error

**Solutions:**

1. Check Twilio credentials are set correctly
2. Verify phone number format: `+1234567890`
3. Check Twilio account has credits
4. Verify phone number is verified (trial account)
5. Check Twilio console for delivery status
6. Review function logs in Supabase dashboard

### Issue: Template Variables Not Replaced

**Symptoms:**

* Email/SMS contains `{{variable}}` literally

**Solutions:**

1. Verify variables object includes all template variables
2. Check variable names match exactly (case-sensitive)
3. Verify template syntax: `{{variable_name}}`
4. Check function logs for variable replacement

### Issue: Rate Limit Exceeded

**Symptoms:**

* 429 errors from email provider or Twilio
* Notifications fail

**Solutions:**

1. Implement rate limiting in application
2. Batch notifications when possible
3. For Entra/Gmail, check tenant or Google project quotas
4. Use `send-pending-notifications` for batch processing
5. Add retry logic with backoff

### Issue: High Costs

**Symptoms:**

* Unexpected charges from Twilio (or cloud provider)

**Solutions:**

1. Monitor usage in provider dashboards
2. Implement rate limiting per organization
3. Use email digests for non-urgent notifications
4. Prefer email over SMS when possible
5. Set spending limits in Twilio console

***

## Best Practices

### 1. Email Best Practices

* **Use verified domains** (Entra: org mailbox; Gmail: Workspace domain)
* **Set up SPF/DKIM/DMARC** per your domain
* **Monitor bounce rates** and handle bounces
* **Use templates** for consistency
* **Test templates** before production use
* **Monitor delivery** via Supabase logs and provider dashboards (Graph / Gmail)

### 2. SMS Best Practices

* **Use SMS only for urgent notifications**
* **Keep messages concise** (\< 160 characters)
* **Include opt-out instructions** (if required)
* **Verify phone numbers** before sending
* **Monitor delivery rates** in Twilio console
* **Handle failed deliveries** gracefully

### 3. Security

* **Never log full email addresses** or phone numbers
* **Sanitize user input** in templates
* **Validate email/phone formats** before sending
* **Use HTTPS** for all API calls
* **Rotate API keys** regularly
* **Monitor for abuse** (unusual sending patterns)

***

## Related Documentation

* **Edge Functions:** `docs/integrations/EDGE_FUNCTIONS.md`
* **Environment Variables:** `docs/development/ENVIRONMENT_VARIABLES.md`
* **Secrets Management:** `docs/security/SECRETS_MANAGEMENT.md`
* **Notifications:** `specs/pf/specs/PF-10-notification-system.md`

***

**Document Owner:** Platform Team\
**Review Frequency:** Quarterly\
**Last Updated:** 2025-01-07
