Files
dynamic-sites-simple/server/src/sms/reply.ts
khalid@traclabs.com c182d34a66 Add logging
2026-05-06 21:58:28 -05:00

97 lines
3.0 KiB
TypeScript

import { Vonage } from '@vonage/server-sdk';
import { SMS } from '@vonage/messages';
import { Auth } from '@vonage/auth';
import { logger, maskPhone } from '../logger.js';
const VONAGE_API_KEY = process.env.VONAGE_API_KEY || '';
const VONAGE_API_SECRET = process.env.VONAGE_API_SECRET || '';
const VONAGE_APPLICATION_ID = process.env.VONAGE_APPLICATION_ID || '';
const VONAGE_PRIVATE_KEY_PATH = process.env.VONAGE_PRIVATE_KEY_PATH || '';
const VONAGE_PRIVATE_KEY = process.env.VONAGE_PRIVATE_KEY || '';
let vonageClient: Vonage | null = null;
/**
* Resolve the private key for the Vonage SDK.
*
* Supports three modes (checked in order):
* 1. VONAGE_PRIVATE_KEY — raw PEM string (starts with "-----BEGIN")
* 2. VONAGE_PRIVATE_KEY — base64-encoded PEM (decode first)
* 3. VONAGE_PRIVATE_KEY_PATH — path to a .key file on disk
*
* The Vonage Auth constructor accepts either a file path (string) or
* the key content directly (string / Buffer).
*/
function resolvePrivateKey(): string | undefined {
if (VONAGE_PRIVATE_KEY) {
// Raw PEM passed directly
if (VONAGE_PRIVATE_KEY.startsWith('-----BEGIN')) {
return VONAGE_PRIVATE_KEY;
}
// Base64-encoded PEM (common for PaaS env vars)
try {
const decoded = Buffer.from(VONAGE_PRIVATE_KEY, 'base64').toString('utf-8');
if (decoded.startsWith('-----BEGIN')) return decoded;
} catch { /* fall through */ }
// Treat as raw content anyway
return VONAGE_PRIVATE_KEY;
}
if (VONAGE_PRIVATE_KEY_PATH) {
return VONAGE_PRIVATE_KEY_PATH;
}
return undefined;
}
function getVonageClient(): Vonage | null {
if (vonageClient) return vonageClient;
const privateKey = resolvePrivateKey();
if (!VONAGE_APPLICATION_ID || !privateKey) {
return null;
}
vonageClient = new Vonage(new Auth({
apiKey: VONAGE_API_KEY,
apiSecret: VONAGE_API_SECRET,
applicationId: VONAGE_APPLICATION_ID,
privateKey,
}));
return vonageClient;
}
/**
* Send an SMS reply via the Vonage Messages API.
* Phone numbers should be in E.164 format (e.g. +14155550100).
* Vonage expects numbers without the leading '+'.
*/
export async function sendSms(to: string, from: string, body: string): Promise<void> {
const client = getVonageClient();
if (!client) {
logger.warn({ event: 'sms.send_skipped', to: maskPhone(to) }, 'No Vonage credentials configured, skipping SMS send');
logger.info({ event: 'sms.would_send', body }, 'SMS body (dev mode)');
return;
}
// Strip leading '+' — Vonage expects bare numbers
const toNumber = to.replace(/^\+/, '');
const fromNumber = from.replace(/^\+/, '');
try {
await client.messages.send(
new SMS({ to: toNumber, from: fromNumber, text: body })
);
logger.info({ event: 'sms.sent', to: maskPhone(to) }, 'SMS sent');
} catch (err) {
const error = err as Error & { response?: { data?: unknown; status?: number } };
logger.error({
event: 'sms.send_error',
error: error.message,
status: error.response?.status,
responseData: error.response?.data,
}, 'SMS send error');
}
}