Publishing Amazon SNS message to Microsoft Teams.

Nanthan Rasiah
4 min readFeb 13, 2021

--

Microsoft teams is a popular communication tool and recently it has gained much interest in the industry as result of rich feature sets. MS teams provides Incoming Webhook connector to easily send notification in real time from external services. Applications/services in AWS often need to send notification to MS teams channel in order to alert users about issues/status.

This post explains how to send notification to MS teams using AWS SNS and AWS lambda function and also provides example cloud formation templates to create necessary resources in AWS.

At present, AWS doesn’t have any service to send notification directly to MS teams. Only option is to create lambda function for this purpose. AWS lambda is serverless computing service (https://aws.amazon.com/lambda/) and support a number of languages. The example AWS Lambda provided below uses Python language.

Let’s assume business requirement is that vehicle IOT sensor data need to be analysed for any anomalies and alert the repair team for prompt action if anything issue detected. Repair team uses MS teams channel to receive notification. Solutions should be implemented in AWS cloud with minimal management overhead.

The following describes the high level solutions.

  1. Streaming service, Amazon Kinesis Stream (https://aws.amazon.com/kinesis/data-streams/) will receive sensor data in real time.
  2. AWL Lambda function analysis the data for anomaly detection.
  3. If any anomaly detected, details are persisted in dynamodb(https://aws.amazon.com/dynamodb/) and send the notification message to Amazon SNS topic. Amazon SNS (https://aws.amazon.com/sns/) provides pub/sub functionality using topics.
  4. Notification message should be in standard format so that solution can be easily extended to send notification to other services as well.
  5. AWS Lambda function is subscribed to Amazon SNS topic to receive notification.
  6. Upon receiving message, AWS Lambda function send notification message to MS teams channel.

Though, end to end solution architecture is described here at high level, detailed implementation of AWS Lambda function that receives notification message from Amazon SNS Topic and send it to MS teams channel is provided below. Note AWS doesn’t provide any services to directly integrate with MS teams. AWS Chatbot (https://aws.amazon.com/chatbot/) enables easy integration between AWS services and Slack channels or Amazon, but not with MS teams yet.

Here is the cloud formation to provision AWS Lambda function to send notification and recommended notification message format. You can customise it as per your needs. Besides, you need to provide MS teams webhook URL, channel name, username, thumbnail and topic ARN when creating cloud formation stack.

Sample Message Format

data class Notification(
@JsonProperty("type")
val type: NotificationType = NotificationType.INFO,
@JsonProperty("header")
val header: String,
@JsonProperty("subHeader")
val subHeader: String = EMPTY,
@JsonProperty("text")
val text: String,
@JsonProperty("fields")
val fields: Map<String, String> = mapOf(),
@JsonProperty("submittedBy")
val submittedBy: String? = null,
@JsonProperty("submittedDate")
val submittedDate: Instant = Instant.now()
)
/**
* Enumeration representing the set of available for notification type
*/
enum class NotificationType {
ERROR,
WARN,
INFO;
}

Cloud Formation Template

AWSTemplateFormatVersion: '2010-09-09'
Description: 'This stack deploys lambda function to send MS team notification.'

Metadata:
'AWS::CloudFormation::Interface':
ParameterGroups:
- Label:
default: 'SNS Topic ARN'
Parameters:
- SNSTopicARN
- Label:
default: 'Team Parameters'
Parameters:
- TeamWebhookURL
- TeamChannelName
- TeamChannelUsername
- TeamThumbnailUrl

Parameters:

SNSTopicARN:
Type: String
Description: 'Amazon SNS Topic ARN (Amazon Resource Name).'
SNSTopicKMSKeyARN:
Type: String
Description: 'KMS encryption key ARN (needed only if encryption enabled).'
TeamWebhookURL:
Type: String
Description: 'Team incoming webhook URL for notification channel.'
TeamChannelName:
Type: String
Description: 'Team notification channel name.'
TeamChannelUsername:
Type: String
Description: 'Team notification channel user name.'
TeamThumbnailUrl:
Type: String
Description: 'Team notification thumbnail URL.'

Resources:
LambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: team_notification_lambda_role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ["lambda.amazonaws.com"]
Action: ["sts:AssumeRole"]
Path: /
Policies:
- PolicyName: team_notification
PolicyDocument:
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
- Effect: Allow
Action:
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
- kms:CreateGrant
- kms:DescribeKey
Resource: '*'

LambdaFunction:
Type: AWS::Lambda::Function
DependsOn: LambdaRole
Properties:
Description: Sending team notification
Environment:
Variables:
envType: !Ref EnvType
webhookUrl : !Ref TeamWebhookURL
channelName : !Ref TeamChannelName
thumbnailUrl : !Ref TeamThumbnailUrl
username : !Ref TeamChannelUsername
FunctionName: !Sub 'team-${TeamChannelName}-channel-notification-function'
Role: !GetAtt LambdaRole.Arn
Timeout: 10
Handler: index.lambda_handler
Runtime: python3.8
Code:
ZipFile: |
import urllib3
import json
import os
import datetime
import logging
import dateutil.tz

logger = logging.getLogger()
logger.setLevel(logging.INFO)

http = urllib3.PoolManager()

url = os.environ['webhookUrl']
env = os.environ['envType']
channelName = os.environ['channelName']
thumbnailUrl = os.environ['thumbnailUrl']
username = os.environ['username']

sydneyTz = dateutil.tz.gettz('Australia/Sydney')

def lambda_handler(event, context):

message = event['Records'][0]['Sns']['Message']
logger.info('Incoming Event: {0}.'.format(message))
try:
parsed_message = json.loads(message)
message_type = parsed_message['type']
time_string = parsed_message['submittedDate']
date_time = datetime.datetime.strptime(time_string, "%Y-%m-%dT%H:%M:%S.%fZ")

color = 'd63333'
if message_type == 'INFO':
color = '0076D7'

msg = {
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": "0076D7",
"title": username,
"summary": parsed_message['header'],
"sections": [{
"activityTitle": parsed_message['header'],
"activitySubtitle": parsed_message['subHeader'],
"activityImage": thumbnailUrl,
"activityText": parsed_message['text'],
"facts": [{
"name": "Environment",
"value": env
}, {
"name": "Created On",
"value": datetime.datetime.fromtimestamp(date_time.timestamp(),sydneyTz).strftime("%d/%m/%Y %H:%M:%S")
}],
"markdown": 'true'
}]
}
encoded_msg = json.dumps(msg).encode('utf-8')
logger.info('Sending Event: {0}.'.format(encoded_msg))
resp = http.request('POST',url, body=encoded_msg)
logger.info('status_code: {0},response: {1}.'.format(resp.status,resp.data))
except Exception as e:
logging.error("Exception occurred", exc_info=True)

LambdaSubscription:
Type: AWS::SNS::Subscription
Properties:
Endpoint: !GetAtt LambdaFunction.Arn
Protocol: lambda
TopicArn: {'Fn::ImportValue': !Sub '${ParentIngestionServiceStack}-SNSNotificationTopicArn'}

LambdaFunctionPermission:
Condition: HasAlertTopic
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt LambdaFunction.Arn
Principal: sns.amazonaws.com
SourceArn: {'Fn::ImportValue': !Sub '${ParentIngestionServiceStack}-SNSNotificationTopicArn'}

LogsLogGroup:
Type: AWS::Logs::LogGroup
DependsOn: LambdaFunction
Properties:
LogGroupName: !Sub "/aws/lambda/${LambdaFunction}"
RetentionInDays: 30

Outputs:
StackName:
Description: 'Stack name.'
Value: !Sub '${AWS::StackName}'
Export:
Name: !Sub '${AWS::StackName}-stack-name'

LambdaFunctionArn:
Description: 'Lambda Function Arn'
Value: !GetAtt LambdaFunction.Arn
Export:
Name: !Sub '${AWS::StackName}-LambdaFunctionArn'

LambdaRoleArn:
Description: 'Lambda Role Arn'
Value: !GetAtt LambdaRole.Arn
Export:
Name: !Sub '${AWS::StackName}-LambdaRoleArn'

Happy Reading! Please get in touch with me for any further information.

--

--

Nanthan Rasiah

Ex. AWS APN Ambassador | Architect | AWS Certified Pro | GCP Certified Pro | Azure Certified Expert | AWS Certified Security & Machine Learning Specialty