A Go-based AWS Lambda function that forwards SNS messages to Slack channels with flexible routing and rich message formatting.
AWS does not provide a direct integration to Slack. They suggest using a Lambda for forward messages, but the example they provide is extremely limited in functionality. This solutions provides the following robust features:
- Flexible Routing: Route messages to different Slack channels based on SNS topic ARN patterns and message subjects
- Rich Formatting: Beautiful Slack Block Kit messages with color coding based on severity
- Smart Configuration: Support for inline JSON or AWS SSM Parameter Store
- JSON Pretty-Printing: Automatically formats JSON messages for readability
- First-Match Routing: Routes are evaluated in order, first match wins
- Fallback Channel: Optional default channel for messages that don't match any route
- Message Truncation: Intelligent truncation for long messages with length indicators
- Lambda is triggered via SNS notification
- The Lambda determines the appropriate Slack channel to forward to based on configured routing rules.
- If a rule is matched, the Lambda forwards the SNS notification to the desired Slack channel.
- Go 1.21 or later
- AWS Account with Lambda permissions
- Slack workspace and bot token with
chat:writepermission - SNS topics to monitor
# Build Lambda function
make build
# Package for deployment
make package-lambdaThis creates dist/function.zip ready for Lambda deployment.
-
Build and package:
make package-lambda
-
Create Lambda function:
aws lambda create-function \ --function-name sns-slack-forwarder \ --runtime provided.al2023 \ --role arn:aws:iam::123456789012:role/lambda-execution-role \ --handler bootstrap \ --zip-file fileb://dist/function.zip \ --timeout 30 \ --memory-size 256
-
Configure environment variables:
aws lambda update-function-configuration \ --function-name sns-slack-forwarder \ --environment Variables="{ SLACK_TOKEN=arn:aws:ssm:us-east-1:123456789012:parameter/slack/bot-token, SLACK_DEFAULT_CHANNEL=C01234567890, SNS_ROUTES_PARAM_ARN=arn:aws:ssm:us-east-1:123456789012:parameter/sns-forwarder/routes, FALLBACK_ENABLED=true, DEBUG_ENABLED=false }"
-
Subscribe to SNS topics:
aws sns subscribe \ --topic-arn arn:aws:sns:us-east-1:123456789012:prod-alerts \ --protocol lambda \ --notification-endpoint arn:aws:lambda:us-east-1:123456789012:function:sns-slack-forwarder
-
Grant SNS permission to invoke Lambda:
aws lambda add-permission \ --function-name sns-slack-forwarder \ --statement-id sns-invoke \ --action lambda:InvokeFunction \ --principal sns.amazonaws.com \ --source-arn arn:aws:sns:us-east-1:123456789012:prod-alerts
The Lambda execution role needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": ["ssm:GetParameter"],
"Resource": [
"arn:aws:ssm:us-east-1:123456789012:parameter/sns-forwarder/*",
"arn:aws:ssm:us-east-1:123456789012:parameter/slack/*"
]
}
]
}See examples/iam-policy.json for a complete example.
Configure via environment variables (see .env.example):
Required:
SLACK_TOKEN- Slack bot token (or SSM parameter ARN)SLACK_DEFAULT_CHANNEL- Default channel ID for fallback
Routing (choose one):
SNS_ROUTES- Inline JSON routing configurationSNS_ROUTES_PARAM_ARN- SSM parameter containing routing config (takes precedence)
Optional:
FALLBACK_ENABLED- Send to default channel if no route matches (default:true)DEBUG_ENABLED- Enable debug logging (default:false)VALIDATE_SNS_SIGNATURES- Validate SNS signatures (default:false)NOTIFY_ON_SUBSCRIPTION- Notify when SNS subscription confirmed (default:false)
export SNS_ROUTES='[
{
"name": "production-alerts",
"sns_topic_arn": "arn:aws:sns:us-east-1:123456789012:prod-alerts",
"slack_channel_id": "C01234567890",
"enabled": true
},
{
"name": "error-pattern",
"sns_topic_arn_pattern": ".*-errors$",
"message_subject_pattern": "ERROR|CRITICAL",
"slack_channel_id": "C09876543210",
"enabled": true
}
]'# Store routing configuration in SSM
aws ssm put-parameter \
--name /sns-forwarder/routes \
--type String \
--value file://examples/routes.json
# Configure Lambda to use SSM parameter
export SNS_ROUTES_PARAM_ARN=arn:aws:ssm:us-east-1:123456789012:parameter/sns-forwarder/routesNote: If SNS_ROUTES_PARAM_ARN is set, it takes precedence over SNS_ROUTES.
Each route can specify:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique name for the route |
sns_topic_arn |
string | No* | Exact SNS topic ARN to match |
sns_topic_arn_pattern |
string | No* | Regex pattern for topic ARN |
message_subject_pattern |
string | No | Regex pattern for message subject (optional filter) |
slack_channel_id |
string | Yes | Slack channel ID to send to |
enabled |
boolean | Yes | Whether this route is active |
* Must specify either sns_topic_arn or sns_topic_arn_pattern
Routes are evaluated in order from top to bottom:
- Check if route is
enabled - Check if topic ARN matches (exact or pattern)
- Check if subject matches pattern (if specified)
- First matching route wins - message is sent to that channel
- If no routes match and
FALLBACK_ENABLED=true, send to default channel - If no routes match and fallback disabled, message is skipped
Messages are formatted using Slack Block Kit with:
- Header: Topic name with alert emoji
- Metadata Section: Subject and timestamp
- Message Body: Formatted message content
- JSON is automatically pretty-printed with 2-space indentation
- Code blocks for better readability
- Truncated at 2500 characters with length indicator
- Footer: Region, account ID, and message ID
- Color Sidebar: Based on subject keywords
- π΄ Red:
error,critical,fatal,fail - π‘ Orange:
warn,caution,alert - π’ Green:
success,complete,info - π΅ Blue: Default
- π΄ Red:
- Go to api.slack.com/apps
- Click "Create New App" β "From scratch"
- Name your app and select workspace
Add these OAuth scopes under "OAuth & Permissions":
chat:write- Send messageschat:write.public- Send to public channels without joining
- Click "Install to Workspace"
- Copy the "Bot User OAuth Token" (starts with
xoxb-) - Invite bot to channels:
/invite @your-bot-name
In Slack, right-click a channel β "View channel details" β Copy the channel ID (starts with C)
aws-sns-slack-forwarder-go/
βββ cmd/lambda/ # Lambda entry point
βββ internal/
β βββ app/ # Application logic
β βββ config/ # Configuration management
β βββ sns/ # SNS message parsing
β βββ slack/ # Slack client and formatting
βββ examples/ # Example configurations
βββ Makefile # Build targets
βββ README.md # This file
make build # Build Lambda binary
make package-lambda # Build and package as zip
make test # Run tests
make test-verbose # Run tests with verbose output
make test-coverage # Run tests with coverage report
make fmt # Format code
make clean # Remove build artifacts
make tidy # Tidy dependenciesThe project includes comprehensive unit tests following the patterns from cruxstack/github-ops-app.
# Run all tests
make test
# Run with verbose output
make test-verbose
# Generate coverage report
make test-coverage
# Opens coverage.html in browserTest Coverage:
- SNS Parser: 94.7% coverage
- Slack Formatter: 76.0% coverage
- Router Logic: 31.0% coverage
Test Structure:
internal/sns/parser_test.go- SNS message parsing and metadata extractioninternal/app/router_test.go- Routing logic with pattern matchinginternal/slack/formatter_test.go- Slack Block Kit formatting and truncationinternal/app/testdata.go- Test data factories (type-safe, no JSON fixtures)
Create a test event file (test-event.json):
{
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:us-east-1:123456789012:test:abc",
"Sns": {
"Type": "Notification",
"MessageId": "abc-123",
"TopicArn": "arn:aws:sns:us-east-1:123456789012:prod-alerts",
"Subject": "ERROR: Application failure",
"Message": "{\"service\": \"api\", \"error\": \"Connection timeout\"}",
"Timestamp": "2025-01-15T10:30:00.000Z"
}
}
]
}Test locally:
export SLACK_TOKEN=xoxb-your-token
export SLACK_DEFAULT_CHANNEL=C01234567890
export SNS_ROUTES='[{"name":"test","sns_topic_arn":"arn:aws:sns:us-east-1:123456789012:prod-alerts","slack_channel_id":"C01234567890","enabled":true}]'
# Build and test
make build
./dist/bootstrap < test-event.json-
Check Lambda logs:
aws logs tail /aws/lambda/sns-slack-forwarder --follow
-
Verify bot permissions:
- Ensure bot has
chat:writescope - Bot must be invited to channels
- Ensure bot has
-
Check routing:
- Enable
DEBUG_ENABLED=trueto see route matching details - Verify topic ARN patterns match your SNS topics
- Enable
-
Invalid SSM parameter:
Error: failed to fetch routes from SSM parameter- Verify SSM parameter exists
- Check IAM permissions for
ssm:GetParameter - Ensure parameter ARN is correct format
-
Route validation failed:
Error: route[0] (test): must specify either sns_topic_arn or sns_topic_arn_pattern- Check JSON syntax in routing configuration
- Ensure all required fields are present
Messages longer than 2500 characters are automatically truncated with a notice. This is a Slack Block Kit limitation. The original message length is shown in the truncation notice.
See the examples/ directory for:
- routes.json - Example routing configuration
- iam-policy.json - Lambda IAM policy
MIT