SNS
SNS broadcasts messages. A publisher sends a message to a Topic once, and SNS pushes a copy to every subscription: a Lambda Function, an SQS Queue, an HTTPS endpoint, an email address. The publisher doesn’t know who’s listening, so adding a subscriber never changes the code that publishes.
A Topic delivers a copy to everyone
Section titled “A Topic delivers a copy to everyone”A Queue hands each message to one consumer. A Topic hands each message to every subscriber, each with its own copy.
SNS pushes each copy as soon as it arrives and retries deliveries that fail. It doesn’t store messages for later, so a subscriber that needs to work at its own pace, or survive being down for a while, subscribes through a Queue.
Declare a Topic
Section titled “Declare a Topic”import * as AWS from "@alchemy.run/aws";
export const Orders = AWS.SNS.Topic("Orders");For messages that must arrive in order and only once, make it a FIFO Topic, and subscribe FIFO Queues to it:
export const Orders = AWS.SNS.Topic("Orders", { fifo: true, attributes: { ContentBasedDeduplication: "true" },});Changing fifo replaces the Topic. Everything else updates in place.
Publish
Section titled “Publish”Bind SNS.Publish in the publisher. Its role gets sns:Publish on this Topic only:
const publish = yield* AWS.SNS.Publish(Orders);yield* publish({ Message: JSON.stringify({ orderId }), Subject: "OrderCreated" });Provide AWS.SNS.PublishHttp to the runtime. To publish up to ten messages per call, bind SNS.PublishBatch. To publish a whole Stream, bind SNS.TopicSink, which batches it for you, and provide AWS.SNS.TopicSinkHttp with AWS.SNS.PublishBatchHttp beneath it.
Subscribe a Function
Section titled “Subscribe a Function”A Lambda Function subscribes from Construction and receives each notification in a Stream:
export default class Audit extends AWS.Lambda.Function<Audit>()( "Audit", { main: import.meta.url, functionUrl: false }, Effect.gen(function* () { const orders = yield* Orders; yield* AWS.SNS.consumeTopicNotifications(orders, (stream) => stream.pipe(Stream.runForEach((n) => Console.log(`${n.Subject}: ${n.Message}`))), ); return {}; }).pipe(Effect.provide(AWS.Lambda.TopicEventSource)),) {}Deploy creates the subscription and the Lambda permission that lets SNS invoke the Function. Each notification carries the Message, Subject, MessageId and TopicArn.
Fan out to Queues
Section titled “Fan out to Queues”The usual shape is one Queue per subscriber. Each Queue gets its own copy and is drained by its own consumer, at its own pace, with its own retries and dead-letter queue. A subscription is a resource of its own:
const orders = yield* Orders;const emails = yield* AWS.SQS.Queue("EmailJobs");
yield* AWS.SNS.Subscription("EmailSubscription", { topicArn: orders.topicArn, protocol: "sqs", endpoint: emails.queueArn, attributes: { RawMessageDelivery: "true" },});SNS needs permission to send to the Queue. Give it in the Queue’s own policy, limited to this Topic:
const emails = yield* AWS.SQS.Queue("EmailJobs", { policy: { Version: "2012-10-17", Statement: [ { Effect: "Allow", Principal: { Service: "sns.amazonaws.com" }, Action: "sqs:SendMessage", Resource: "*", Condition: { ArnEquals: { "aws:SourceArn": orders.topicArn } }, }, ], },});RawMessageDelivery delivers the message body as is. Without it, each message arrives wrapped in a JSON envelope with the subject and Topic. From here, each Queue is an ordinary SQS consumer.
Filter what each subscriber sees
Section titled “Filter what each subscriber sees”A subscriber rarely wants every message. A filter policy tells SNS which ones to deliver, matched on message attributes, so the rest never invoke the subscriber at all:
yield* AWS.SNS.consumeTopicNotifications( orders, { attributes: { FilterPolicy: JSON.stringify({ type: ["order.created"] }) } }, (stream) => stream.pipe(Stream.runForEach((n) => Console.log(n.Message))),);Publishers set the attributes to match on with MessageAttributes. A Subscription takes the same FilterPolicy in its attributes.
SNS, SQS or EventBridge
Section titled “SNS, SQS or EventBridge”All three move messages between parts of an app, and each fits a different shape.
Use an SQS Queue when one consumer should do each piece of work, at its own pace, with retries. Use an SNS Topic when several independent subscribers should each react to the same message. Use EventBridge when many kinds of events share one bus and rules route each one by its content, including events from AWS services themselves and schedules.