Skip to main content
All Lambda function code lives in the functions/ directory. The following tools and conventions are used in the project.
ToolPurpose
pipenvDependency management and virtualenv
pytest + snapshottestUnit and snapshot testing
blackCode formatting
isortImport statement formatting
flake8 + radonLinting and static analysis
mypyStatic type checking

Prerequisites

Install pipenv before running any of the commands below.

Install dependencies

From within the functions/ directory, install all Python dependencies including development tools:
pipenv install --dev
If you add or change entries in Pipfile, sync your local virtualenv:
pipenv update

Development dependencies (from Pipfile)

PackageVersionPurpose
boto3~=1.34AWS SDK (mocked in tests)
botocore~=1.34AWS SDK core
pytestlatestTest runner
pytest-covlatestCoverage reports
snapshottest~=0.6Snapshot testing
blacklatestCode formatter
isortlatestImport sorter
flake8latestLinter
radonlatestCode complexity analysis
mypylatestType checker

Running tests

The Pipfile defines named scripts for all common tasks. Run them from within the functions/ directory.

Unit tests

pipenv run test
This runs pytest with coverage reporting (--cov --cov-report=term). The test file is notify_slack_test.py.

All available scripts

ScriptCommandDescription
testpython3 -m pytest --cov --cov-report=termRun tests with coverage
test:updatesnapshotspython3 -m pytest --snapshot-updateRegenerate snapshot files
coverpython3 -m coverage htmlGenerate HTML coverage report
lintpython3 -m flake8 . --count --statistics ...Show linting errors (non-blocking)
lint:cipython3 -m flake8 . --config=.flake8Lint with exit code (for CI)
formatpython3 -m black .Auto-format all Python files
importspython3 -m isort . --profile blackSort import statements
typecheckpython3 -m mypy . --ignore-missing-importsRun type checks
complexitypython3 -m radon cc notify_slack.py -aShow cyclomatic complexity
halsteadpython3 -m radon hal notify_slack.pyShow Halstead metrics

Directory structure

functions/
├── notify_slack.py          # Main Lambda function
├── notify_slack_test.py     # Unit and snapshot tests
├── mylambda.py              # Template for custom Lambda functions
├── integration_test.py      # Integration tests (requires live Slack webhook)
├── Pipfile                  # Python dependency definitions
├── events/                  # Raw AWS event JSON payloads
├── messages/                # SNS message payloads as delivered to Lambda
├── snapshots/               # Snapshot test output files
├── .flake8                  # Flake8 configuration
└── .pyproject.toml          # Black and isort configuration

events/

Contains raw event JSON files as they are emitted by AWS services (for example, guardduty_finding.json or cloudwatch_alarm.json). These match the event formats documented in the AWS documentation.

messages/

Contains SNS message payloads in the form delivered to the Lambda function. The Message attribute holds the payload that is parsed and sent to Slack — this can be an event from events/ or any plain string.

snapshots/

Contains snapshot files generated by snapshottest. Each snapshot records the expected Slack message payload for a given input. Tests fail if the output differs from the stored snapshot, making regressions visible immediately.
Snapshots should only change when the expected Slack output changes intentionally. When you update a snapshot, explain why in your pull request.

Snapshot testing workflow

When you change the output format of any formatter, update the snapshots:
1

Regenerate snapshot files

pipenv run test:updatesnapshots
2

Auto-format the generated code

The snapshot files follow their own style; run the formatter to keep them consistent:
pipenv run format
3

Verify the diff

Review the snapshot diff in your pull request. Only the snapshots for your new or changed formatters should appear in the diff.

Using mylambda.py as a template

The mylambda.py file is a minimal custom Lambda function you can use as a starting point when you need behaviour different from the built-in notify_slack.py.
def lambda_handler(event, context):
    url = os.environ["SLACK_WEBHOOK_URL"]
    msg = {
        "channel": "#channel-name",
        "username": "Prometheus",
        "text": event["Records"][0]["Sns"]["Message"],
        "icon_emoji": "",
    }
    encoded_msg = json.dumps(msg).encode("utf-8")
    resp = http.request("POST", url, body=encoded_msg)
To use a custom function, copy mylambda.py to your Terraform root module and set:
module "notify_slack" {
  source  = "terraform-aws-modules/notify-slack/aws"

  lambda_source_path = "./my_custom_function.py"
  # ...
}
The handler name is derived automatically from the filename — my_custom_function.py becomes my_custom_function.lambda_handler.

Integration tests

Integration tests send real notifications to a live Slack channel. They require a deployed module and an active webhook URL.
1

Create a test Slack channel and webhook

Set up a dedicated Slack channel as a sandbox and create an incoming webhook for it. See the Slack incoming webhooks documentation for details.
2

Deploy the example module

From within examples/notify-slack-simple/, set your Slack variables and apply:
slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC"
slack_channel     = "aws-notification"
slack_username    = "reporter"
terraform init && terraform apply -y
3

Run the integration tests

From within the functions/ directory:
pipenv run python integration_test.py
One Slack message should arrive per file in functions/events/ and functions/messages/.
4

Clean up

Destroy the provisioned resources when you are done:
terraform destroy -y

Adding support for new event types

To add a new event type with custom formatting:
1

Add a sample event payload

Create a JSON file in functions/events/ named using snake case: <service>_<event_type>.json (for example, codepipeline_execution.json).
2

Write a formatter function

In notify_slack.py, add a function named format_<service>_<event_type>(). Follow the existing pattern: accept message and region arguments and return a Slack attachment dict.
3

Add severity enum (optional)

If the event has severity levels that map to Slack colour bars, create an enum following the CloudWatchAlarmState or GuardDutyFindingSeverity pattern.
4

Register the event in `parse_notification()`

Add a detection condition to parse_notification() that calls your new formatter.
5

Update snapshots

pipenv run test:updatesnapshots
pipenv run format
Verify that only your new snapshot appears in the diff.

Build docs developers (and LLMs) love