Skip to content

Integrations: SMS and Telegram

More channels under the same MessagingProvider contract — swap channels without touching business code.

Telegram

A Bot API client (no SDK). Sends over REST, receives via long-polling.

import { TelegramProvider } from "tempest-express-sdk";

const tg = new TelegramProvider({ token: process.env.TELEGRAM_BOT_TOKEN! });

await tg.sendText("<chat_id>", "Hello from the bot 🤖");
await tg.sendMedia("<chat_id>", { kind: "image", media: "https://.../photo.jpg", caption: "hi" });

// Receive (long-polling getUpdates):
const stop = await tg.onMessage((msg) => {
  console.log("received:", msg.from, msg.text);
});
// later: await stop();

SMS (Twilio)

A Twilio REST client. Sending is dependency-free; receiving is via webhook (SMS has no persistent subscription, so this provider has no onMessage).

import { TwilioSmsProvider } from "tempest-express-sdk";

const sms = new TwilioSmsProvider({
  accountSid: process.env.TWILIO_SID!,
  authToken: process.env.TWILIO_TOKEN!,
  from: "+15550000000",
});

await sms.sendText("+15551112222", "Your code is 1234");

Inbound webhook (validated)

makeTwilioWebhookRouter validates the X-Twilio-Signature (HMAC-SHA1) and delivers a typed InboundMessage.

import { createApp, makeTwilioWebhookRouter } from "tempest-express-sdk";

const app = await createApp({
  configure: (a) => {
    a.use(
      makeTwilioWebhookRouter({
        authToken: process.env.TWILIO_TOKEN!,
        publicUrl: "https://api.yourco.com/sms/inbound", // behind a proxy
        onMessage: async (msg) => {
          await handleSms(msg); // { from, messageId, text, ... }
        },
      }),
    );
  },
});

Invalid signature → 401. Responds with an empty <Response></Response> (TwiML).

Email as a channel

EmailProvider also implements MessagingProvider (over EmailUtils), so email notifications use the same interface.

import { EmailProvider, EmailUtils } from "tempest-express-sdk";

const email = new EmailUtils({ host: "smtp.example.com", from: "no-reply@example.com" });
const provider = new EmailProvider({ email, subject: "Notice" });

await provider.sendText("ana@example.com", "Your order is out for delivery");

Email has no onMessage/checkNumber (send-only).

Program against the contract

import type { MessagingProvider } from "tempest-express-sdk";

// Works with WhatsApp, Telegram or SMS — inject the provider.
async function notify(provider: MessagingProvider, to: string, text: string) {
  await provider.sendText(to, text);
}

Broadcast and multi-channel

broadcastText sends one message to many recipients through a provider, with bounded concurrency and a per-recipient result (one failure never aborts the batch). MessagingHub registers providers by name.

import { MessagingHub, broadcastText } from "tempest-express-sdk";

const results = await broadcastText(wa, ["5511...", "5521..."], "Promo!", { concurrency: 20 });
const failures = results.filter((r) => !r.ok);

const hub = new MessagingHub()
  .register("whatsapp", wa)
  .register("sms", sms)
  .register("email", emailProvider);

await hub.send("sms", "+15551112222", "Code 1234");
await hub.broadcast("whatsapp", numbers, "Notice");

Per-channel capabilities

sendText/sendMedia/status exist on all. onMessage exists where there is a live subscription (WhatsApp /ws, Telegram polling); checkNumber is WhatsApp-only. Check the method is present before calling it in generic code.

Recap

WhatsApp, Telegram and SMS share MessagingProvider. Telegram receives via polling; SMS via a signed webhook; all send through the same interface.