4.5 KiB
AWS SNS — @suaveplan/sms-providers
(no submodules)
Purpose
Real AWS SNS SMS provider implementing SmsProvider from @suaveplan/sms. POSTs Action=Publish form bodies to the regional SNS endpoint (https://sns.<region>.amazonaws.com/) signed with inline AWS Signature V4 (see ./sigv4); SMS-specific message attributes (AWS.SNS.SMS.SMSType and AWS.SNS.SMS.SenderID) are forwarded when supplied on the provider config. Inbound SNS HTTP/S notifications — subscription confirmation, delivery callbacks, unsubscribe confirmation — are validated through verifyNotification which delegates the RSA-SHA1 verification step to an injectable callback so this package never imports node:crypto.
The class holds no in-flight state and routes every HTTP call through the injectable FetchLike seam. Tests stand up a node:http server on a random port and inject a fetch adapter that points at it.
Features
- Inline AWS Signature V4 signing (no
@aws-sdkdep tree). AWS.SNS.SMS.SMSTypedefaults toTransactional; override viaconfig.smsType: "Promotional".AWS.SNS.SMS.SenderIDforwarded from the messagefromor the configsenderId.- Optional
sessionTokenfor STS-issued credentials (X-Amz-Security-Token). - Status mapping covers
pending/success/failure/optout. verifyNotification(payload, rsaVerify)validates the documented SNS signing chain — fetches the signing cert URL through the injectable fetch, builds the canonical string-to-sign (different field order forNotificationvsSubscriptionConfirmation/UnsubscribeConfirmation), and delegates the RSA verification to the supplied callback.parseInbounddiscriminates between delivery-receipt notifications (Subject ="AWS SMS Delivery Receipt") and inbound mobile-originated SMS payloads; recognises CTIA opt-out keywords on the inbound body.- OTel — counter + histogram on every send + verify.
Usage: Basic
import { AwsSnsProvider } from "@suaveplan/sms-providers";
const sns = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "AKIA…",
secretAccessKey: "…",
senderId: "SUAVEPLAN",
});
const r = await sns.send({ to: "+15551234567", body: "Hello" });
Usage: Advanced
import { AwsSnsProvider } from "@suaveplan/sms-providers";
import { createVerify } from "node:crypto";
const sns = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "AKIA…",
secretAccessKey: "…",
sessionToken: "FwoGZXIvYXdz…",
smsType: "Transactional",
});
// Node-side RSA-SHA1 verifier callback.
const rsaVerify = async (pem: string, msg: string, sigB64: string) =>
createVerify("RSA-SHA1").update(msg).end().verify(pem, sigB64, "base64");
const ok = await sns.verifyNotification(
/* payload */ {
Type: "Notification",
MessageId: "abc",
TopicArn: "arn:aws:sns:…",
Subject: "AWS SMS Delivery Receipt",
Message: JSON.stringify({ status: "SUCCESS" }),
Timestamp: new Date().toISOString(),
Signature: "<base64>",
SignatureVersion: "1",
SigningCertURL:
"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-XXX.pem",
},
rsaVerify
);
API Reference
| Symbol | Kind | Notes |
|---|---|---|
AwsSnsProvider |
class | Implements SmsProvider. |
send(message, options?) |
method | SigV4-signed POST Action=Publish. |
getStatus(id) |
method | Returns a synthetic queued report (SNS does not expose per-message status). |
mapStatus(raw) |
method | Canonical mapping. |
verifyNotification(payload, rsaVerify) |
method | Delegates RSA-SHA1 verification to the supplied callback. |
parseInbound(payload) |
method | Discriminates into InboundSmsEvent. |
buildSnsStringToSign(payload) |
helper | Exported for advanced use. |
Implementation Notes
getStatus deliberately returns a synthetic queued report rather than synthesise a fake delivered — SNS reports SMS lifecycle exclusively via the configured delivery-status SNS topic (typically wired into CloudWatch Logs in production), and lying about provider behaviour would mislead consumers. The recommended production pipeline is to subscribe to the delivery-status topic, route the notifications back through the provider's parseInbound, and feed the resulting delivery_receipt events to @suaveplan/sms's DeliveryTracker. The senderId config overrides the per-message from; this matches the AWS console UX where the account-level Sender ID is the production default.