# Send SMS Messages with Zavu

> Send SMS messages programmatically to any phone number worldwide. REST API, a TypeScript SDK, smart routing across carriers.

SMS (Short Message Service) is the most universal messaging channel, reaching virtually any mobile phone without requiring an internet connection or app installation.

## When to Use SMS

- **Verification codes**: OTPs, 2FA
- **Transactional alerts**: Order confirmations, shipping updates
- **Time-sensitive notifications**: Appointment reminders
- **Universal reach**: When you need to reach users without smartphones

## Sending an SMS

```typescript
const result = await zavu.messages.send({
  to: "+14155551234",
  text: "Your verification code is 123456",
  channel: "sms",
});
```

```bash
curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155551234",
    "text": "Your verification code is 123456",
    "channel": "sms"
  }'
```

> **Tip:**
> You can optionally specify a sender by passing `"Zavu-Sender": "snd_abc123"` inside the send params (TypeScript), or the `Zavu-Sender` header (cURL). If omitted, your project's default sender is used.

## Message Length

SMS messages are limited to 160 characters (GSM-7 encoding) or 70 characters (Unicode). Longer messages are split into multiple segments.

| Encoding | Single SMS | Concatenated SMS |
|----------|------------|------------------|
| GSM-7 | 160 chars | 153 chars/segment |
| Unicode | 70 chars | 67 chars/segment |

> **Tip:**
> Keep messages under 160 characters when possible to avoid multi-segment charges.

## Character Encoding

GSM-7 supports basic Latin characters. Using emojis, accents, or non-Latin scripts triggers Unicode encoding:

```
GSM-7:    "Your code is 123456"          (20 chars, 1 segment)
Unicode:  "Your code is 123456"         (22 chars, 1 segment with special chars)
Unicode:  "Tu codigo es 123456"           (19 chars, 1 segment, 'o' with accent triggers Unicode)
```

### GSM-7 Character Set

```
A-Z a-z 0-9
@ $ _ ! " # % & ' ( ) * + , - . / : ; < = > ?
Space, newline, carriage return
```

Any character outside this set triggers Unicode encoding.

## Sender ID

Some countries support alphanumeric sender IDs (e.g., "ZAVU" instead of a phone number).

> **Warning:**
> Alphanumeric sender IDs cannot receive replies. Use a phone number if you need two-way SMS.

### Supported Countries

| Region | Alphanumeric Sender ID |
|--------|------------------------|
| US/Canada | Not supported (10DLC or short code required) |
| UK | Supported |
| EU | Mostly supported |
| LATAM | Varies by country |

## SMS Templates

Send pre-defined templates via SMS by specifying `channel: "sms"` and `messageType: "template"`:

```typescript
const result = await zavu.messages.send({
  to: "+14155551234",
  channel: "sms",
  messageType: "template",
  content: {
    templateId: "tpl_abc123",
    templateVariables: {
      "1": "John",
      "2": "ORD-12345",
    },
  },
});
```

```bash
curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155551234",
    "channel": "sms",
    "messageType": "template",
    "content": {
      "templateId": "tpl_abc123",
      "templateVariables": { "1": "John", "2": "ORD-12345" }
    }
  }'
```

If the template has an `smsBody`, it will be used instead of the default `body`. Contact variables like `{{contact.first_name}}` are automatically resolved from the recipient's contact metadata.

## Delivery Status

Check message status by ID:

```bash
curl https://api.zavu.dev/v1/messages/msg_abc123 \
  -H "Authorization: Bearer zv_live_xxx"
```

| Status | Description |
|--------|-------------|
| `queued` | Accepted, pending delivery |
| `sent` | Sent to carrier |
| `delivered` | Confirmed delivered |
| `failed` | Delivery failed |

## Common Errors

| Error Code | Description | Solution |
|------------|-------------|----------|
| `30003` | Unreachable destination | Invalid number or phone off |
| `30004` | Message blocked | Content filtered by carrier |
| `30005` | Unknown destination | Number doesn't exist |
| `30006` | Landline destination | Cannot send SMS to landlines |
| `30007` | Carrier violation | Message rejected by carrier |

## Compliance

> **Warning:**
> SMS messaging is regulated. Ensure you have consent before sending messages.

### Requirements

- **Opt-in consent**: Users must explicitly agree to receive SMS
- **Opt-out mechanism**: Include instructions to unsubscribe (e.g., "Reply STOP to unsubscribe")
- **Business identification**: Identify your business in the message
- **Time restrictions**: Avoid sending during quiet hours (typically 9 PM - 9 AM local time)

### Content check

Every SMS and one-way SMS with text is read before it is sent. A message the
check refuses is answered right away with `422` and code
`sms_compliance_blocked`; nothing is sent or charged. One-time codes
(verification codes) always pass.

```json
{
  "code": "sms_compliance_blocked",
  "message": "SMS blocked: The message asks for a payment through a link without identifying the sender.",
  "details": {
    "riskScore": 85,
    "messageType": "marketing",
    "complianceFailures": [
      { "code": "MISSING_SENDER_ID", "severity": "high", "title": "Sender not identified" }
    ],
    "requiredFixes": ["Name your business in the message."],
    "suggestedCompliantVersion": "[YourBrand] Your invoice is ready: https://yourbrand.com/pay/123"
  }
}
```

Apply `requiredFixes`, or send `suggestedCompliantVersion` when there is one,
and send again. If the check itself could not run, `message` says so; retry in
a few minutes.

> **Note:**
> This error used to arrive wrapped, as `{"error": {"code": ..., "message": ..., "details": ...}}`.
> It now has the same flat `{code, message, details}` shape as every other API
> error. If your code reads `error.code` for this case, read `code` instead.

### Example Compliant Message

```
[YourBrand] Your order #12345 has shipped! Track: https://track.co/abc

Reply STOP to unsubscribe
```

## Best Practices

1. **Keep it short** - Aim for under 160 characters
2. **Identify yourself** - Start with your brand name
3. **Be actionable** - Include clear next steps or links
4. **Respect timing** - Send during business hours in the recipient's timezone
5. **Provide opt-out** - Always include unsubscribe instructions for marketing messages
