Add TWILIO_SENDER_SID override and better funnel/setup error messages

This commit is contained in:
Peter Steinberger
2025-11-24 12:36:03 +01:00
parent 6c6e217f83
commit e52e943317
2 changed files with 52 additions and 30 deletions

View File

@@ -7,6 +7,7 @@ Small TypeScript CLI to send, monitor, and webhook WhatsApp messages via Twilio.
1. `pnpm install` 1. `pnpm install`
2. Copy `.env.example` to `.env` and fill in `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_WHATSAPP_FROM` (use your approved WhatsApp-enabled Twilio number, prefixed with `whatsapp:`). 2. Copy `.env.example` to `.env` and fill in `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_WHATSAPP_FROM` (use your approved WhatsApp-enabled Twilio number, prefixed with `whatsapp:`).
- Alternatively, use API keys: `TWILIO_API_KEY` + `TWILIO_API_SECRET` instead of `TWILIO_AUTH_TOKEN`. - Alternatively, use API keys: `TWILIO_API_KEY` + `TWILIO_API_SECRET` instead of `TWILIO_AUTH_TOKEN`.
- Optional: `TWILIO_SENDER_SID` to skip auto-discovery of the WhatsApp sender in Twilio.
3. Build once for the runnable bin: `pnpm build` 3. Build once for the runnable bin: `pnpm build`
## Commands ## Commands

View File

@@ -46,6 +46,7 @@ type GlobalOptions = {
type EnvConfig = { type EnvConfig = {
accountSid: string; accountSid: string;
whatsappFrom: string; whatsappFrom: string;
whatsappSenderSid?: string;
auth: AuthMode; auth: AuthMode;
}; };
@@ -53,6 +54,7 @@ function readEnv(): EnvConfig {
// Load and validate Twilio auth + sender configuration from env. // Load and validate Twilio auth + sender configuration from env.
const accountSid = process.env.TWILIO_ACCOUNT_SID; const accountSid = process.env.TWILIO_ACCOUNT_SID;
const whatsappFrom = process.env.TWILIO_WHATSAPP_FROM; const whatsappFrom = process.env.TWILIO_WHATSAPP_FROM;
const whatsappSenderSid = process.env.TWILIO_SENDER_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN; const authToken = process.env.TWILIO_AUTH_TOKEN;
const apiKey = process.env.TWILIO_API_KEY; const apiKey = process.env.TWILIO_API_KEY;
const apiSecret = process.env.TWILIO_API_SECRET; const apiSecret = process.env.TWILIO_API_SECRET;
@@ -79,6 +81,7 @@ function readEnv(): EnvConfig {
return { return {
accountSid, accountSid,
whatsappFrom, whatsappFrom,
whatsappSenderSid,
auth auth
}; };
} }
@@ -445,6 +448,7 @@ async function ensureFunnel(port: number) {
async function findWhatsappSenderSid(client: ReturnType<typeof createClient>, from: string) { async function findWhatsappSenderSid(client: ReturnType<typeof createClient>, from: string) {
// Fetch sender SID that matches configured WhatsApp from number. // Fetch sender SID that matches configured WhatsApp from number.
try {
const resp = await (client as unknown as { request: (options: Record<string, unknown>) => Promise<{ data?: unknown }> }).request({ const resp = await (client as unknown as { request: (options: Record<string, unknown>) => Promise<{ data?: unknown }> }).request({
method: 'get', method: 'get',
uri: 'https://messaging.twilio.com/v2/Channels/Senders', uri: 'https://messaging.twilio.com/v2/Channels/Senders',
@@ -455,7 +459,7 @@ async function findWhatsappSenderSid(client: ReturnType<typeof createClient>, fr
? (data as { senders: unknown[] }).senders ? (data as { senders: unknown[] }).senders
: undefined; : undefined;
if (!senders) { if (!senders) {
throw new Error('Unable to list WhatsApp senders'); throw new Error('List senders response missing "senders" array');
} }
const match = senders.find( const match = senders.find(
(s) => (s) =>
@@ -467,6 +471,16 @@ async function findWhatsappSenderSid(client: ReturnType<typeof createClient>, fr
throw new Error(`Could not find sender ${withWhatsAppPrefix(from)} in Twilio account`); throw new Error(`Could not find sender ${withWhatsAppPrefix(from)} in Twilio account`);
} }
return match.sid; return match.sid;
} catch (err) {
console.error(danger('Unable to list WhatsApp senders via Twilio API.'));
if (globalVerbose) console.error(err);
console.error(
info(
'Provide TWILIO_SENDER_SID in .env to skip lookup (find it in Twilio Console → Messaging → Senders → WhatsApp).'
)
);
process.exit(1);
}
} }
async function updateWebhook( async function updateWebhook(
@@ -476,15 +490,22 @@ async function updateWebhook(
method: 'POST' | 'GET' = 'POST' method: 'POST' | 'GET' = 'POST'
) { ) {
// Point Twilio sender webhook at the provided URL. // Point Twilio sender webhook at the provided URL.
await (client as unknown as { request: (options: Record<string, unknown>) => Promise<unknown> }).request({ await (client as unknown as { request: (options: Record<string, unknown>) => Promise<unknown> })
.request({
method: 'post', method: 'post',
uri: `https://messaging.twilio.com/v2/Channels/Senders/${senderSid}`, uri: `https://messaging.twilio.com/v2/Channels/Senders/${senderSid}`,
form: { form: {
CallbackUrl: url, CallbackUrl: url,
CallbackMethod: method CallbackMethod: method
} }
})
.catch((err) => {
console.error(danger('Failed to set Twilio webhook.'));
if (globalVerbose) console.error(err);
console.error(info('Double-check your sender SID and credentials; you can set TWILIO_SENDER_SID to force a specific sender.'));
process.exit(1);
}); });
console.log(`✅ Twilio webhook set to ${url}`); console.log(success(`✅ Twilio webhook set to ${url}`));
} }
function sleep(ms: number) { function sleep(ms: number) {