nversary congratulates people on their work anniversary in Slack.
Anniversary messages are sent on working days only, with a maximum of 3 messages per day. If there are more than 3 anniversaries on nearby dates, they are spread out so that people with longer tenure get the message closest to their actual anniversary day.
The full setup, in the order it needs to happen:
- Install tooling — Task, Terraform (
>= 1.14.0), Node.js + npm. See Deployment prerequisites. - Configure AWS credentials for an account in
eu-west-1. See AWS credentials. - Create the S3 people-data object with the employee JSON. See People data (S3 object).
- Create the SSM
SecureStringparameter with the Slack config. See Slack. - Export the required environment variables (
PEOPLE_S3_BUCKET,PEOPLE_S3_KEY,SSM_PARAMETER_NAME). See Environment variables. - Deploy with
task deploy:plan:devto preview, thentask deploy:devto apply. See Deploy with Task.
Steps 1–2 and 5 are hard prerequisites for terraform apply; the S3 object and SSM parameter (steps 3–4) are only read by the Lambda at runtime, so deployment succeeds without them but the Lambda will fail until they exist.
How to set up and configure nversary
An AWS Account is required. If you don't have one, create it at https://aws.amazon.com/
Terraform and the AWS CLI authenticate using the standard AWS credential chain (~/.aws/credentials, environment variables, or SSO) — nversary does not read any AWS_* variables of its own. The target region is hardcoded to eu-west-1 in the Terraform providers and backends, so your default region does not matter, but your credentials must be valid for the account you want to deploy into.
The credentials need permissions to manage: S3 (the Terraform state bucket), IAM roles/policies, Lambda, CloudWatch Logs, and EventBridge.
Sign in with aws configure (or aws sso login) and verify with:
aws sts get-caller-identityPeople data is read from s3://$PEOPLE_S3_BUCKET/$PEOPLE_S3_KEY.
Expected shape:
{
"people": [
{
"fullName": "Example Person",
"email": "example.person@example.com",
"presence": [{ "start": "2018-02-01" }],
"position": "Senior Consultant",
"businessUnit": "Technology",
"profileImageUrl": "https://example.com/image.jpg",
"slackId": "U0123456789"
}
]
}Notes:
slackIdis optional but recommended; when present, nversary mentions that Slack user directly.- If
slackIdis missing, nversary falls back to matching Slack users byemail. profileImageUrlis optional.
- Go to https://api.slack.com/apps and click Create New App, give your app a name and attach it to a workspace.
- In OAuth & Permissions, add bot token scopes:
chat:writeusers:readusers:read.email
- Install the app to the workspace and save the Bot User OAuth Token.
- Invite bot to the target channel:
/invite @botname - Store credentials to AWS SSM Parameter Store as
SecureString.
The JSON in SSM Parameter Store looks similar to this:
{
"slack": {
"webhookUrl": "",
"appToken": "xoxb-....",
"channelId": "JO3KFSO5"
}
}webhookUrlis currently unused by the runtime (kept for backward compatibility with the existing config model).appTokenis Bot User OAuth Token from Features/OAuth & Permissions.channelIdis the identifier for channel where messages are sent. You can obtain this from Slack UI/Chat app.
nversary uses Terraform for deployment.
Terraform layout:
terraform/modules/nversary_notifierreusable moduleterraform/infra/envs/devdevelopment environment rootterraform/infra/envs/prodproduction environment rootterraform/remote-statebootstrap for Terraform backend state bucket
Both environments use an S3 backend (backend "s3" {}) configured in:
terraform/infra/envs/dev/backend.tfterraform/infra/envs/prod/backend.tf
Deployment values come from Terraform input variables and static values in terraform/infra/envs/*/main.tf:
nameandenvironmentruntimeandtimeoutpeople_s3_bucketandpeople_s3_key(pass at apply/plan time)ssm_parameter_name(pass at apply/plan time)artifact_file(local path to the Lambda zip)log_retention_days
Current environment scheduling:
dev: disabled schedule (cron(0 0 31 2 ? *))prod: daily at03:50 UTC(cron(50 3 * * ? *))
Slack send behavior is controlled by the Lambda environment variable SLACK_DRY_RUN.
dev: configurable at deploy time viaSLACK_DRY_RUN, defaults totrueprod: alwaysfalse(messages are always sent)
Examples:
# dev default (dry-run enabled)
task deploy:dev
# dev override (send real Slack messages)
SLACK_DRY_RUN=false task deploy:dev
# prod (always dry-run=false)
task deploy:prodInstall:
- Task
- Terraform (
>= 1.14.0) - Node.js + npm
Before running any deploy or plan command, export these variables. Task validates them in its check-env step and aborts the deploy if any is missing. The same three values are also injected as the Lambda's runtime environment variables.
# Required — validated by `task`; deploy aborts if any is unset
export PEOPLE_S3_BUCKET=your-people-bucket
export PEOPLE_S3_KEY=path/to/people.json
export SSM_PARAMETER_NAME=/path/to/slack-config
# Optional — dev only, defaults to true; prod always sends real messages
export SLACK_DRY_RUN=falsePersist them so you don't have to remember next time. Save the exports once to a local, git-ignored file (e.g. .env.local) and source it before deploying:
set -a; source .env.local; set +a
task deploy:plan:devKeep that file out of version control (it names your real bucket and parameter) — add it to .gitignore. If you use direnv, an .envrc with the same exports loads them automatically when you cd into the project. (Task can also auto-load a dotenv via a dotenv: directive in Taskfile.yml if you later want that built in.)
Plan/apply for development:
task deploy:plan:dev
task deploy:devPlan/apply for production:
task deploy:plan:prod
task deploy:prodTask workflow does all of the following:
- validates required environment variables
- bootstraps Terraform remote state from
terraform/remote-state/main.tfif needed - packages the Lambda artifact zip for the selected environment (
build/dev/nversary.ziporbuild/prod/nversary.zip) - runs Terraform
init,plan, andapplyin the matching environment root
You do not need to build the artifact separately — packaging happens automatically as part of plan/apply.
npm run testYou can test the Lambda function from AWS Lambda console by creating a test event with a dateString attribute.
The date string should be in yyyy-MM-dd format.
Setting sendNow to true, will send messages immediately. An example of test event:
{
"dateString": "2022-04-25",
"sendNow": true
}