Skip to main content

Best Practices

Follow these best practices to ensure reliable, efficient, and compliant use of the Communications API.

🔐 Security & Authentication

API Key Management

✅ Do:

  • Store API keys in environment variables or secure vaults
  • Use different keys for sandbox and production environments
  • Implement API key rotation policies
  • Monitor API key usage and detect anomalies

❌ Don't:

  • Hardcode API keys in source code
  • Share API keys in public repositories
  • Use production keys in development/testing
  • Log API keys in application logs
// ✅ Good
const apiKey = process.env.COMMUNICATIONS_API_KEY;

// ❌ Bad
const apiKey = "live_key_abc123def456"; // Hardcoded

Request Security

  • Always use HTTPS for API requests
  • Validate inputs before sending to API
  • Implement timeout handling for network requests
  • Use request signing when available

📱 Message Content Guidelines

SMS Best Practices

Content Optimization

// ✅ Good - Clear, concise, actionable
"Parent's Evening: Tue 15th Nov 6-8pm. Book: school.com/booking Ref: PE2024"

// ❌ Poor - Too verbose, unclear action
"We are writing to inform you that our school will be hosting a parent-teacher consultation evening next Tuesday..."

Character Management

  • Single SMS: Keep under 160 characters when possible
  • Concatenated SMS: Use up to 918 characters (6 parts) if needed
  • Unicode awareness: Non-ASCII characters count as 2 characters
  • Test thoroughly: Always test with actual character counts

Email Best Practices

Subject Lines

// ✅ Good - Specific and actionable
"Action Required: Parent's Evening Booking Closes Friday"

// ❌ Poor - Vague and generic
"Important School Information"

Content Structure

  • Mobile-first: 60%+ of emails are opened on mobile
  • Clear CTAs: Use obvious calls-to-action
  • Alt text: Include alt text for images
  • Plain text fallback: Always provide plain text version

🚀 Performance Optimization

Rate Limiting Strategy

class MessageQueue {
constructor(rateLimit = 100) {
this.queue = [];
this.processing = false;
this.rateLimit = rateLimit; // messages per second
}

async addMessage(message) {
this.queue.push(message);
if (!this.processing) {
this.process();
}
}

async process() {
this.processing = true;

while (this.queue.length > 0) {
const batch = this.queue.splice(0, this.rateLimit);
await this.sendBatch(batch);

// Wait 1 second before next batch
if (this.queue.length > 0) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}

this.processing = false;
}
}

Bulk Messaging

Batch Optimization

  • Optimal batch size: 50-100 messages per request
  • Parallel processing: Use multiple concurrent requests
  • Progress tracking: Implement progress indicators for large sends
  • Failure handling: Retry failed batches with exponential backoff
import asyncio
import aiohttp

async def send_bulk_messages(messages, batch_size=50):
batches = [messages[i:i + batch_size] for i in range(0, len(messages), batch_size)]

async with aiohttp.ClientSession() as session:
tasks = [send_batch(session, batch) for batch in batches]
results = await asyncio.gather(*tasks, return_exceptions=True)

return results

📊 Monitoring & Observability

Delivery Tracking

Implement Comprehensive Logging

const messageLog = {
messageId: 'msg_123',
customerId: 'customer_456',
type: 'SMS',
status: 'sent',
recipient: '+447700900123',
timestamp: new Date().toISOString(),
units: 1,
reference: 'ref_789'
};

// Log to your monitoring system
logger.info('Message sent', messageLog);

Key Metrics to Track

  • Delivery rates by message type and customer
  • Response times for API requests
  • Error rates and failure patterns
  • Cost tracking per message/customer
  • User engagement metrics

Error Handling

Retry Strategy

async function sendWithRetry(messageData, maxRetries = 3) {
let attempt = 0;

while (attempt < maxRetries) {
try {
const result = await sendMessage(messageData);
return result;
} catch (error) {
attempt++;

if (error.status === 429) { // Rate limited
const backoffTime = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, backoffTime));
} else if (error.status >= 500) { // Server error
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
} else {
// Client error - don't retry
throw error;
}
}
}

throw new Error(`Failed after ${maxRetries} attempts`);
}

🏫 Education Sector Compliance

Data Protection

GDPR Compliance

  • Consent management: Track and respect user preferences
  • Data minimization: Only request necessary data
  • Right to erasure: Implement data deletion capabilities
  • Audit trails: Maintain comprehensive logs
// Example consent checking
async function sendMessage(recipient, message) {
const consent = await checkConsent(recipient, 'SMS');

if (!consent.granted) {
throw new Error('Recipient has not consented to SMS messages');
}

if (consent.expiresAt < new Date()) {
throw new Error('Consent has expired');
}

return await sendSms(recipient, message);
}

Student Data Protection

  • Parental consent: Verify consent for under-18 communications
  • School approval: Ensure school authorization for communications
  • Safeguarding: Implement content filtering and approval workflows

Accessibility

Inclusive Design

  • Clear language: Use simple, accessible language
  • Multiple channels: Offer email alternatives to SMS
  • Timing consideration: Respect reasonable hours for communications
  • Language support: Provide multi-language capabilities

🔄 Integration Patterns

Webhook Handling

Reliable Webhook Processing

app.post('/webhook/sms-status', async (req, res) => {
try {
// Acknowledge immediately
res.status(200).send('OK');

// Process asynchronously
await processWebhookAsync(req.body);
} catch (error) {
// Log error but don't fail the webhook
logger.error('Webhook processing failed', error);
}
});

async function processWebhookAsync(webhookData) {
// Validate webhook signature
if (!validateSignature(webhookData)) {
throw new Error('Invalid webhook signature');
}

// Update message status
await updateMessageStatus(webhookData.messageId, webhookData.status);

// Trigger any dependent processes
await triggerFollowUpActions(webhookData);
}

Idempotency

Preventing Duplicate Messages

const sentMessages = new Map();

async function sendIdempotentMessage(messageData) {
const key = generateIdempotencyKey(messageData);

if (sentMessages.has(key)) {
return sentMessages.get(key);
}

const result = await sendMessage(messageData);
sentMessages.set(key, result);

// Clean up old entries
setTimeout(() => sentMessages.delete(key), 24 * 60 * 60 * 1000);

return result;
}

📋 Testing Strategy

Test Environment Setup

Sandbox Testing

const config = {
production: {
apiKey: process.env.PROD_API_KEY,
API_BASE: 'https://m5api.groupcall.com'
},
sandbox: {
apiKey: process.env.SANDBOX_API_KEY,
API_BASE: 'https://m5api.groupcall.com'
}
};

const apiConfig = config[process.env.NODE_ENV] || config.sandbox;

Test Data Management

  • Dedicated test numbers: Use provided sandbox numbers
  • Test scenarios: Cover success, failure, and edge cases
  • Automated testing: Include API tests in CI/CD pipeline
  • Load testing: Test with production-like volumes

Quality Assurance

Pre-Production Checklist

  • All API keys are environment-specific
  • Error handling covers all expected scenarios
  • Rate limiting is implemented and tested
  • Webhook endpoints are secured and tested
  • Compliance requirements are met
  • Monitoring and alerting are configured
  • Documentation is up to date

Additional Resources: