720 lines
16 KiB
Markdown
720 lines
16 KiB
Markdown
# SaaS Application Integration Guide
|
|
|
|
## Overview
|
|
|
|
This guide shows how to integrate MySMSAPio into your SaaS application to send SMS and OTP messages.
|
|
|
|
## Prerequisites
|
|
|
|
1. **MySMSAPio Server**: Running and accessible (e.g., `http://192.168.18.66:3005`)
|
|
2. **API Key**: Client API key (starts with `api_live_...`)
|
|
3. **Gateway Device**: At least one Android gateway online
|
|
|
|
## Quick Start
|
|
|
|
### 1. Create an API Key
|
|
|
|
**Via Admin Interface**:
|
|
1. Go to `http://your-server:3005/admin/api_keys`
|
|
2. Click "New API Key"
|
|
3. Enter name (e.g., "My SaaS App")
|
|
4. Enable permissions: `send_sms`, `receive_sms`
|
|
5. Copy the API key (shown only once!)
|
|
|
|
**Via Rails Console**:
|
|
```ruby
|
|
result = ApiKey.generate!(
|
|
name: "My SaaS App",
|
|
permissions: {
|
|
send_sms: true,
|
|
receive_sms: true,
|
|
send_otp: true,
|
|
verify_otp: true
|
|
}
|
|
)
|
|
puts result[:raw_key]
|
|
# Save this key: api_live_abc123...
|
|
```
|
|
|
|
### 2. Test Connection
|
|
|
|
```bash
|
|
curl -X GET http://your-server:3005/up \
|
|
-H "Authorization: Bearer api_live_your_key_here"
|
|
```
|
|
|
|
Expected: `200 OK`
|
|
|
|
## API Endpoints
|
|
|
|
### Base URL
|
|
```
|
|
http://your-server:3005/api/v1
|
|
```
|
|
|
|
### Authentication
|
|
All requests require the `Authorization` header:
|
|
```
|
|
Authorization: Bearer api_live_your_key_here
|
|
```
|
|
|
|
## Sending SMS
|
|
|
|
### Endpoint
|
|
```
|
|
POST /api/v1/sms/send
|
|
```
|
|
|
|
### Request
|
|
```json
|
|
{
|
|
"to": "+959123456789",
|
|
"message": "Your message here"
|
|
}
|
|
```
|
|
|
|
### Response (Success - 202 Accepted)
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message_id": "msg_abc123def456...",
|
|
"status": "queued"
|
|
}
|
|
```
|
|
|
|
### Response (Error - 422 Unprocessable Entity)
|
|
```json
|
|
{
|
|
"error": "Invalid phone number format"
|
|
}
|
|
```
|
|
|
|
### Example: cURL
|
|
```bash
|
|
curl -X POST http://your-server:3005/api/v1/sms/send \
|
|
-H "Authorization: Bearer api_live_your_key_here" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"to": "+959123456789",
|
|
"message": "Hello from My SaaS App!"
|
|
}'
|
|
```
|
|
|
|
### Example: JavaScript/Node.js
|
|
```javascript
|
|
const axios = require('axios');
|
|
|
|
async function sendSMS(phoneNumber, message) {
|
|
try {
|
|
const response = await axios.post(
|
|
'http://your-server:3005/api/v1/sms/send',
|
|
{
|
|
to: phoneNumber,
|
|
message: message
|
|
},
|
|
{
|
|
headers: {
|
|
'Authorization': 'Bearer api_live_your_key_here',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log('SMS sent!');
|
|
console.log('Message ID:', response.data.message_id);
|
|
console.log('Status:', response.data.status);
|
|
|
|
return response.data.message_id;
|
|
} catch (error) {
|
|
console.error('Failed to send SMS:', error.response?.data || error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
sendSMS('+959123456789', 'Welcome to our service!')
|
|
.then(messageId => console.log('Sent:', messageId))
|
|
.catch(err => console.error('Error:', err));
|
|
```
|
|
|
|
### Example: Python
|
|
```python
|
|
import requests
|
|
|
|
def send_sms(phone_number, message):
|
|
url = "http://your-server:3005/api/v1/sms/send"
|
|
headers = {
|
|
"Authorization": "Bearer api_live_your_key_here",
|
|
"Content-Type": "application/json"
|
|
}
|
|
data = {
|
|
"to": phone_number,
|
|
"message": message
|
|
}
|
|
|
|
response = requests.post(url, json=data, headers=headers)
|
|
|
|
if response.status_code == 202:
|
|
result = response.json()
|
|
print(f"SMS sent! Message ID: {result['message_id']}")
|
|
return result['message_id']
|
|
else:
|
|
print(f"Failed: {response.json()}")
|
|
raise Exception(response.json().get('error', 'Unknown error'))
|
|
|
|
# Usage
|
|
message_id = send_sms("+959123456789", "Welcome to our service!")
|
|
```
|
|
|
|
### Example: PHP
|
|
```php
|
|
<?php
|
|
function sendSMS($phoneNumber, $message) {
|
|
$url = 'http://your-server:3005/api/v1/sms/send';
|
|
|
|
$data = [
|
|
'to' => $phoneNumber,
|
|
'message' => $message
|
|
];
|
|
|
|
$options = [
|
|
'http' => [
|
|
'header' => [
|
|
"Authorization: Bearer api_live_your_key_here",
|
|
"Content-Type: application/json"
|
|
],
|
|
'method' => 'POST',
|
|
'content' => json_encode($data)
|
|
]
|
|
];
|
|
|
|
$context = stream_context_create($options);
|
|
$result = file_get_contents($url, false, $context);
|
|
|
|
if ($result === FALSE) {
|
|
throw new Exception('Failed to send SMS');
|
|
}
|
|
|
|
$response = json_decode($result, true);
|
|
echo "SMS sent! Message ID: " . $response['message_id'] . "\n";
|
|
|
|
return $response['message_id'];
|
|
}
|
|
|
|
// Usage
|
|
$messageId = sendSMS("+959123456789", "Welcome to our service!");
|
|
?>
|
|
```
|
|
|
|
### Example: Ruby
|
|
```ruby
|
|
require 'httparty'
|
|
|
|
def send_sms(phone_number, message)
|
|
response = HTTParty.post(
|
|
'http://your-server:3005/api/v1/sms/send',
|
|
headers: {
|
|
'Authorization' => 'Bearer api_live_your_key_here',
|
|
'Content-Type' => 'application/json'
|
|
},
|
|
body: {
|
|
to: phone_number,
|
|
message: message
|
|
}.to_json
|
|
)
|
|
|
|
if response.code == 202
|
|
puts "SMS sent! Message ID: #{response['message_id']}"
|
|
response['message_id']
|
|
else
|
|
puts "Failed: #{response['error']}"
|
|
raise StandardError, response['error']
|
|
end
|
|
end
|
|
|
|
# Usage
|
|
message_id = send_sms("+959123456789", "Welcome to our service!")
|
|
```
|
|
|
|
## Sending OTP
|
|
|
|
### Endpoint
|
|
```
|
|
POST /api/v1/otp/send
|
|
```
|
|
|
|
### Request
|
|
```json
|
|
{
|
|
"phone_number": "+959123456789"
|
|
}
|
|
```
|
|
|
|
### Response (Success - 200 OK)
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "OTP sent successfully",
|
|
"expires_in": 300
|
|
}
|
|
```
|
|
|
|
### Response (Error - Rate Limited)
|
|
```json
|
|
{
|
|
"error": "Rate limit exceeded. Maximum 3 OTP per hour for this number"
|
|
}
|
|
```
|
|
|
|
### Example: JavaScript
|
|
```javascript
|
|
async function sendOTP(phoneNumber) {
|
|
try {
|
|
const response = await axios.post(
|
|
'http://your-server:3005/api/v1/otp/send',
|
|
{
|
|
phone_number: phoneNumber
|
|
},
|
|
{
|
|
headers: {
|
|
'Authorization': 'Bearer api_live_your_key_here',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log('OTP sent!');
|
|
console.log('Expires in:', response.data.expires_in, 'seconds');
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Failed to send OTP:', error.response?.data || error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
sendOTP('+959123456789')
|
|
.then(() => console.log('OTP sent successfully'))
|
|
.catch(err => console.error('Error:', err));
|
|
```
|
|
|
|
### Example: Python
|
|
```python
|
|
def send_otp(phone_number):
|
|
url = "http://your-server:3005/api/v1/otp/send"
|
|
headers = {
|
|
"Authorization": "Bearer api_live_your_key_here",
|
|
"Content-Type": "application/json"
|
|
}
|
|
data = {
|
|
"phone_number": phone_number
|
|
}
|
|
|
|
response = requests.post(url, json=data, headers=headers)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
print(f"OTP sent! Expires in {result['expires_in']} seconds")
|
|
return True
|
|
else:
|
|
print(f"Failed: {response.json()}")
|
|
raise Exception(response.json().get('error', 'Unknown error'))
|
|
|
|
# Usage
|
|
send_otp("+959123456789")
|
|
```
|
|
|
|
## Verifying OTP
|
|
|
|
### Endpoint
|
|
```
|
|
POST /api/v1/otp/verify
|
|
```
|
|
|
|
### Request
|
|
```json
|
|
{
|
|
"phone_number": "+959123456789",
|
|
"code": "123456"
|
|
}
|
|
```
|
|
|
|
### Response (Success - 200 OK)
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "OTP verified successfully"
|
|
}
|
|
```
|
|
|
|
### Response (Error - Invalid)
|
|
```json
|
|
{
|
|
"error": "Invalid or expired OTP"
|
|
}
|
|
```
|
|
|
|
### Example: JavaScript
|
|
```javascript
|
|
async function verifyOTP(phoneNumber, code) {
|
|
try {
|
|
const response = await axios.post(
|
|
'http://your-server:3005/api/v1/otp/verify',
|
|
{
|
|
phone_number: phoneNumber,
|
|
code: code
|
|
},
|
|
{
|
|
headers: {
|
|
'Authorization': 'Bearer api_live_your_key_here',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log('OTP verified!');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('OTP verification failed:', error.response?.data || error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const isValid = await verifyOTP('+959123456789', '123456');
|
|
if (isValid) {
|
|
console.log('User verified!');
|
|
} else {
|
|
console.log('Invalid OTP');
|
|
}
|
|
```
|
|
|
|
### Example: Python
|
|
```python
|
|
def verify_otp(phone_number, code):
|
|
url = "http://your-server:3005/api/v1/otp/verify"
|
|
headers = {
|
|
"Authorization": "Bearer api_live_your_key_here",
|
|
"Content-Type": "application/json"
|
|
}
|
|
data = {
|
|
"phone_number": phone_number,
|
|
"code": code
|
|
}
|
|
|
|
response = requests.post(url, json=data, headers=headers)
|
|
|
|
if response.status_code == 200:
|
|
print("OTP verified successfully!")
|
|
return True
|
|
else:
|
|
print(f"Verification failed: {response.json()}")
|
|
return False
|
|
|
|
# Usage
|
|
if verify_otp("+959123456789", "123456"):
|
|
print("User authenticated!")
|
|
else:
|
|
print("Authentication failed")
|
|
```
|
|
|
|
## Checking Message Status
|
|
|
|
### Endpoint
|
|
```
|
|
GET /api/v1/sms/status/:message_id
|
|
```
|
|
|
|
### Response
|
|
```json
|
|
{
|
|
"message_id": "msg_abc123def456...",
|
|
"status": "delivered",
|
|
"sent_at": "2025-10-22T10:30:00Z",
|
|
"delivered_at": "2025-10-22T10:30:05Z",
|
|
"failed_at": null,
|
|
"error_message": null
|
|
}
|
|
```
|
|
|
|
### Status Values
|
|
- `queued` - Waiting for gateway
|
|
- `pending` - No gateway available, will retry
|
|
- `sent` - Sent to gateway device
|
|
- `delivered` - Successfully delivered to recipient
|
|
- `failed` - Failed to send
|
|
|
|
### Example: JavaScript
|
|
```javascript
|
|
async function checkMessageStatus(messageId) {
|
|
try {
|
|
const response = await axios.get(
|
|
`http://your-server:3005/api/v1/sms/status/${messageId}`,
|
|
{
|
|
headers: {
|
|
'Authorization': 'Bearer api_live_your_key_here'
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log('Status:', response.data.status);
|
|
console.log('Sent at:', response.data.sent_at);
|
|
console.log('Delivered at:', response.data.delivered_at);
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Failed to check status:', error.response?.data || error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const status = await checkMessageStatus('msg_abc123def456');
|
|
```
|
|
|
|
## Complete Integration Example
|
|
|
|
### User Registration with OTP (JavaScript)
|
|
|
|
```javascript
|
|
const MySMSAPI = {
|
|
baseURL: 'http://your-server:3005/api/v1',
|
|
apiKey: 'api_live_your_key_here',
|
|
|
|
headers() {
|
|
return {
|
|
'Authorization': `Bearer ${this.apiKey}`,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
},
|
|
|
|
async sendSMS(phoneNumber, message) {
|
|
const response = await axios.post(
|
|
`${this.baseURL}/sms/send`,
|
|
{ to: phoneNumber, message },
|
|
{ headers: this.headers() }
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
async sendOTP(phoneNumber) {
|
|
const response = await axios.post(
|
|
`${this.baseURL}/otp/send`,
|
|
{ phone_number: phoneNumber },
|
|
{ headers: this.headers() }
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
async verifyOTP(phoneNumber, code) {
|
|
try {
|
|
const response = await axios.post(
|
|
`${this.baseURL}/otp/verify`,
|
|
{ phone_number: phoneNumber, code },
|
|
{ headers: this.headers() }
|
|
);
|
|
return response.data.success;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
async checkStatus(messageId) {
|
|
const response = await axios.get(
|
|
`${this.baseURL}/sms/status/${messageId}`,
|
|
{ headers: this.headers() }
|
|
);
|
|
return response.data;
|
|
}
|
|
};
|
|
|
|
// Usage Example: User Registration Flow
|
|
async function registerUser(phoneNumber, userData) {
|
|
try {
|
|
// Step 1: Send OTP
|
|
console.log('Sending OTP...');
|
|
await MySMSAPI.sendOTP(phoneNumber);
|
|
console.log('OTP sent! Check your phone.');
|
|
|
|
// Step 2: Wait for user to enter OTP (in real app, this would be a form)
|
|
const userEnteredCode = '123456'; // Get from user input
|
|
|
|
// Step 3: Verify OTP
|
|
console.log('Verifying OTP...');
|
|
const isValid = await MySMSAPI.verifyOTP(phoneNumber, userEnteredCode);
|
|
|
|
if (isValid) {
|
|
console.log('✅ Phone verified!');
|
|
|
|
// Step 4: Create user account
|
|
const user = await createUserInDatabase(phoneNumber, userData);
|
|
|
|
// Step 5: Send welcome SMS
|
|
const result = await MySMSAPI.sendSMS(
|
|
phoneNumber,
|
|
`Welcome to our service, ${userData.name}! Your account is now active.`
|
|
);
|
|
|
|
console.log('Welcome SMS sent:', result.message_id);
|
|
|
|
return { success: true, user };
|
|
} else {
|
|
console.log('❌ Invalid OTP');
|
|
return { success: false, error: 'Invalid OTP' };
|
|
}
|
|
} catch (error) {
|
|
console.error('Registration failed:', error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// Example usage
|
|
registerUser('+959123456789', { name: 'John Doe', email: 'john@example.com' });
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Common Errors
|
|
|
|
| Status | Error | Cause | Solution |
|
|
|--------|-------|-------|----------|
|
|
| 401 | Unauthorized | Invalid API key | Check API key is correct |
|
|
| 422 | Unprocessable Entity | Invalid phone number | Use international format (+959...) |
|
|
| 429 | Too Many Requests | Rate limit exceeded | Wait before retrying |
|
|
| 404 | Not Found | Message ID not found | Check message ID is correct |
|
|
| 503 | Service Unavailable | No gateway online | Ensure gateway is connected |
|
|
|
|
### Rate Limits
|
|
|
|
- **SMS Send**: 100 per minute per API key
|
|
- **OTP Send**: 3 per hour per phone number
|
|
- **OTP Verify**: 3 attempts per OTP code
|
|
|
|
## Best Practices
|
|
|
|
### 1. Store API Key Securely
|
|
```javascript
|
|
// ❌ Don't hardcode in frontend
|
|
const apiKey = 'api_live_abc123...';
|
|
|
|
// ✅ Use environment variables
|
|
const apiKey = process.env.SMS_API_KEY;
|
|
|
|
// ✅ Store in backend config
|
|
const apiKey = config.get('sms.apiKey');
|
|
```
|
|
|
|
### 2. Handle Errors Gracefully
|
|
```javascript
|
|
async function sendSMSWithRetry(phoneNumber, message, maxRetries = 3) {
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
return await MySMSAPI.sendSMS(phoneNumber, message);
|
|
} catch (error) {
|
|
if (i === maxRetries - 1) throw error;
|
|
await sleep(1000 * (i + 1)); // Exponential backoff
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Validate Phone Numbers
|
|
```javascript
|
|
function validatePhoneNumber(phone) {
|
|
// Myanmar phone numbers
|
|
const myanmarRegex = /^\+959\d{8,9}$/;
|
|
return myanmarRegex.test(phone);
|
|
}
|
|
```
|
|
|
|
### 4. Monitor Message Status
|
|
```javascript
|
|
async function sendAndTrack(phoneNumber, message) {
|
|
const result = await MySMSAPI.sendSMS(phoneNumber, message);
|
|
|
|
// Poll status until delivered
|
|
const maxAttempts = 30; // 5 minutes (30 * 10 seconds)
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
await sleep(10000); // Wait 10 seconds
|
|
|
|
const status = await MySMSAPI.checkStatus(result.message_id);
|
|
|
|
if (status.status === 'delivered') {
|
|
console.log('✅ Message delivered!');
|
|
return status;
|
|
} else if (status.status === 'failed') {
|
|
console.log('❌ Message failed:', status.error_message);
|
|
throw new Error(status.error_message);
|
|
}
|
|
}
|
|
|
|
console.log('⏱️ Delivery timeout');
|
|
return null;
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Test API Key
|
|
Use the API Tester in the admin interface:
|
|
```
|
|
http://your-server:3005/admin/api_tester
|
|
```
|
|
|
|
### Test from Command Line
|
|
```bash
|
|
# Send SMS
|
|
curl -X POST http://your-server:3005/api/v1/sms/send \
|
|
-H "Authorization: Bearer api_live_your_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"to":"+959123456789","message":"Test"}'
|
|
|
|
# Send OTP
|
|
curl -X POST http://your-server:3005/api/v1/otp/send \
|
|
-H "Authorization: Bearer api_live_your_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"phone_number":"+959123456789"}'
|
|
|
|
# Verify OTP
|
|
curl -X POST http://your-server:3005/api/v1/otp/verify \
|
|
-H "Authorization: Bearer api_live_your_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"phone_number":"+959123456789","code":"123456"}'
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Messages Stuck in "pending"
|
|
**Cause**: No gateway online
|
|
**Solution**: Ensure at least one Android gateway is connected
|
|
|
|
### "Invalid phone number" error
|
|
**Cause**: Wrong format
|
|
**Solution**: Use international format: `+[country_code][number]`
|
|
|
|
### "Rate limit exceeded"
|
|
**Cause**: Too many requests
|
|
**Solution**: Implement exponential backoff and caching
|
|
|
|
### "Unauthorized" error
|
|
**Cause**: Invalid or expired API key
|
|
**Solution**: Generate new API key in admin interface
|
|
|
|
## Support
|
|
|
|
- **API Documentation**: See `API_DOCUMENTATION.md`
|
|
- **Admin Interface**: `http://your-server:3005/admin`
|
|
- **API Tester**: `http://your-server:3005/admin/api_tester`
|
|
- **Logs**: `http://your-server:3005/admin/logs`
|
|
|
|
## Summary
|
|
|
|
✅ **Create API Key** → Get `api_live_...` key
|
|
✅ **Send SMS** → `POST /api/v1/sms/send`
|
|
✅ **Send OTP** → `POST /api/v1/otp/send`
|
|
✅ **Verify OTP** → `POST /api/v1/otp/verify`
|
|
✅ **Check Status** → `GET /api/v1/sms/status/:id`
|
|
|
|
Your SaaS app is now ready to send SMS and OTP! 🚀
|