SNS to SQS within the same account is simple. You create a topic, create a queue, and subscribe. Done. But a cross-account SNS to SQS subscription needs more work. When the topic lives in one account and the queue lives in another, you have to set up resource policies on both sides.
This guide covers the IAM policies, CloudFormation resources, and common gotchas for this setup. If you’ve done other cross-account work before — like setting up AssumeRole or accessing Secrets Manager across accounts — this follows a similar pattern.
How Cross-Account SNS to SQS Works
Two AWS accounts are involved in this setup:
- Account A (111111111111) — owns the SNS topic that publishes messages
- Account B (222222222222) — owns the SQS queue that receives messages, plus an optional Lambda function to process them
The message flow is straightforward: SNS topic (Account A) → SQS queue (Account B) → Lambda (Account B).
To make this work, you need three things:
- An SNS topic access policy in Account A allowing Account B to subscribe
- An SQS queue policy in Account B allowing the SNS topic to send messages
- An SNS subscription created from Account B connecting the two
Prerequisites
- Access to both AWS accounts with SNS, SQS, and IAM permissions
- AWS CLI set up for both accounts — see How to Install AWS CLI v2 on Ubuntu 22.04 if needed
- Basic knowledge of IAM resource policies and CloudFormation
Step 1: Configure the SNS Topic Policy in Account A
First, Account A needs to allow Account B to subscribe to its SNS topic. Without this policy, subscription attempts from Account B will fail with an authorization error.
Add this statement to the SNS topic’s access policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountSubscription",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": "sns:Subscribe",
"Resource": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
}
]
}
Replace 222222222222 with Account B’s actual ID. Also update the topic ARN to match your region and topic name.
For tighter security, you can restrict this to SQS protocol only. Just add a condition like this:
{
"Sid": "AllowCrossAccountSQSSubscription",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": "sns:Subscribe",
"Resource": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name",
"Condition": {
"StringEquals": {
"sns:Protocol": "sqs"
}
}
}
The Double Permission Requirement
Here’s something that catches people off guard. Even though Account A allows the subscription in the topic policy, that’s only half the picture. The IAM user or role in Account B also needs sns:Subscribe in their own IAM policy. Both sides have to agree before it works.
CloudFormation Version
In CloudFormation, the topic policy looks like this:
SNSTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
Topics:
- !Ref MyTopic
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: AllowCrossAccountSubscription
Effect: Allow
Principal:
AWS: "arn:aws:iam::222222222222:root"
Action: "sns:Subscribe"
Resource: !Ref MyTopic
Step 2: Configure the SQS Queue Policy in Account B
Next, Account B’s SQS queue needs a policy that allows the SNS service to deliver messages. Without this, SNS drops the messages silently. You won’t get a useful error in most cases.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSNSSendMessage",
"Effect": "Allow",
"Principal": {
"Service": "sns.amazonaws.com"
},
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:ap-southeast-1:222222222222:your-queue-name",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
}
}
}
]
}
The Condition block is critical here. It restricts which SNS topic can send to this queue. Without it, any SNS topic from any account could push messages to your queue.
CloudFormation Version
SQSQueuePolicy:
Type: AWS::SQS::QueuePolicy
Properties:
Queues:
- !Ref MyQueue
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: AllowSNSSendMessage
Effect: Allow
Principal:
Service: sns.amazonaws.com
Action: "sqs:SendMessage"
Resource: !GetAtt MyQueue.Arn
Condition:
ArnEquals:
aws:SourceArn: "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
Step 3: Create the Cross-Account SNS to SQS Subscription
Now that both policies are in place, you can create the subscription. Do this from Account B’s side. It points to Account A’s SNS topic ARN and uses Account B’s SQS queue as the endpoint.
Using CloudFormation
Deploy this resource in Account B’s stack:
CrossAccountSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
Protocol: sqs
Endpoint: !GetAtt MyQueue.Arn
RawMessageDelivery: true
I recommend setting RawMessageDelivery to true. Without it, SNS wraps your message in its own JSON envelope. As a result, your consumer has to parse extra metadata just to reach the actual payload. With raw delivery, the message body arrives exactly as published.
Using AWS CLI
Alternatively, you can use the CLI to test before writing CloudFormation. Run this with Account B’s credentials:
aws sns subscribe \
--topic-arn "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name" \
--protocol sqs \
--notification-endpoint "arn:aws:sqs:ap-southeast-1:222222222222:your-queue-name" \
--attributes '{"RawMessageDelivery":"true"}'
A successful response returns a subscription ARN. However, if you get an authorization error, check both the SNS topic policy in Account A and the IAM permissions in Account B.
Step 4: Test the Setup
To verify, publish a test message from Account A:
aws sns publish \
--topic-arn "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name" \
--message '{"test": "cross-account message"}'
Then poll the SQS queue from Account B:
aws sqs receive-message \
--queue-url "https://sqs.ap-southeast-1.amazonaws.com/222222222222/your-queue-name" \
--max-number-of-messages 1
If you see the message, everything is working. If the queue is empty, check these in order:
- Subscription status — open the SNS console in Account A and confirm it shows “Confirmed,” not “Pending confirmation”
- SQS queue policy — verify the
aws:SourceArncondition matches the SNS topic ARN exactly - Region — both resources must be in the same region for standard topics
Common Pitfalls
Manual Subscription Conflicting with CloudFormation
You’ll probably run into this at some point. If you create a subscription manually through the console to test, and then later deploy the same one through CloudFormation, you get this error:
Subscription already exists with different attributes
To fix it, delete the manual subscription first. Then redeploy through your IaC tool. AWS treats each subscription as unique based on topic ARN, protocol, and endpoint. It won’t create a duplicate even if the attributes differ slightly.
Missing IAM Permissions on Account B
This one confuses people the most. The SNS topic policy in Account A grants account-level permission. However, the IAM user or role in Account B still needs sns:Subscribe in their own policy. It’s a two-layer check — both the resource policy and the identity policy must allow it.
Make sure the role or user in Account B includes at least this statement:
{
"Effect": "Allow",
"Action": "sns:Subscribe",
"Resource": "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
}
Messages Not Arriving in the Queue
When the subscription is confirmed but messages aren’t showing up, the issue is almost always the SQS queue policy. Either the aws:SourceArn doesn’t match exactly, or the policy is missing. You can check CloudTrail logs in Account A for SNS:Publish delivery failures to confirm.
Also, if your SQS queue triggers a Lambda function, make sure the queue’s visibility timeout is higher than the Lambda timeout. Otherwise, messages get reprocessed multiple times.
Full CloudFormation Template for Account B
Here’s a complete template for Account B. It includes the SQS queue, queue policy, and cross-account subscription in a single stack:
AWSTemplateFormatVersion: "2010-09-09"
Description: Cross-account SNS to SQS subscription setup
Parameters:
SNSTopicArn:
Type: String
Description: ARN of the SNS topic in Account A
Default: "arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
Resources:
MessageQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: cross-account-messages
VisibilityTimeout: 300
MessageRetentionPeriod: 1209600
MessageQueuePolicy:
Type: AWS::SQS::QueuePolicy
Properties:
Queues:
- !Ref MessageQueue
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: AllowSNSSendMessage
Effect: Allow
Principal:
Service: sns.amazonaws.com
Action: "sqs:SendMessage"
Resource: !GetAtt MessageQueue.Arn
Condition:
ArnEquals:
aws:SourceArn: !Ref SNSTopicArn
SNSSubscription:
Type: AWS::SNS::Subscription
DependsOn: MessageQueuePolicy
Properties:
TopicArn: !Ref SNSTopicArn
Protocol: sqs
Endpoint: !GetAtt MessageQueue.Arn
RawMessageDelivery: true
Outputs:
QueueArn:
Value: !GetAtt MessageQueue.Arn
QueueUrl:
Value: !Ref MessageQueue
SubscriptionArn:
Value: !Ref SNSSubscription
Deploy the stack with this command:
aws cloudformation deploy \
--template-file cross-account-sns-sqs.yaml \
--stack-name cross-account-sns-sqs \
--parameter-overrides SNSTopicArn="arn:aws:sns:ap-southeast-1:111111111111:your-topic-name"
Adding a Dead-Letter Queue
For production, add a dead-letter queue (DLQ). This catches messages that your consumer fails to process. Instead of disappearing, failed messages move to the DLQ after a set number of retries.
Add these resources to your CloudFormation template:
DeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: cross-account-messages-dlq
MessageRetentionPeriod: 1209600
MessageQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: cross-account-messages
VisibilityTimeout: 300
MessageRetentionPeriod: 1209600
RedrivePolicy:
deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
maxReceiveCount: 3
With maxReceiveCount: 3, a message moves to the DLQ after three failed attempts. You can then set up a CloudWatch alarm on the DLQ to get notified when messages land there.
Wrapping Up
Cross-account SNS to SQS is not complicated once you know the pattern. The SNS topic policy lets Account B subscribe. The SQS queue policy lets SNS deliver messages. And the subscription ties the two together. Most issues come from either missing IAM permissions on Account B or a mismatched ARN in the SQS queue policy.
For more cross-account patterns, check out setting up cross-account S3 uploads with Lambda or copying S3 bucket objects across AWS accounts.


