afterhours-shift-manager/template.yaml
Adam Moussa 9deb8bf251 Email weekly pay summary to payroll via SES on Mondays
Sends an HTML pay summary email to configured payroll recipients
alongside the existing Slack post. Uses SES with noreply@seahaven.com
as sender. Recipients configured via PAYROLL_RECIPIENTS env var
(set to adam@seahaven.com for testing, switch to payroll@seahaven.com
for production).

Closes #3
2026-04-07 18:54:19 -04:00

191 lines
6.1 KiB
YAML

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: After-Hours Shift Manager — Slack bot for managing on-call shifts with 3CX integration
Parameters:
Timezone:
Type: String
Default: "America/New_York"
SchedulerFunctionName:
Type: String
Default: "3cx-ring-group-scheduler"
Description: Name of the existing 3CX ring group scheduler Lambda
Globals:
Function:
Runtime: python3.12
Timeout: 30
MemorySize: 1024
Resources:
# --- DynamoDB ---
ShiftTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: afterhours-shifts
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: PK
AttributeType: S
- AttributeName: SK
AttributeType: S
KeySchema:
- AttributeName: PK
KeyType: HASH
- AttributeName: SK
KeyType: RANGE
# --- Slack Bot Lambda ---
SlackBotFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: afterhours-shift-manager
Handler: src/handler.handler
CodeUri: .
Environment:
Variables:
SHIFT_TABLE: !Ref ShiftTable
SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token
SLACK_SIGNING_SECRET_PARAM: /afterhours-shift-manager/slack-signing-secret
SCHEDULER_FUNCTION_NAME: !Ref SchedulerFunctionName
TZ: !Ref Timezone
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref ShiftTable
- Statement:
- Effect: Allow
Action:
- ssm:GetParameter
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/afterhours-shift-manager/*"
- Effect: Allow
Action:
- kms:Decrypt
Resource: "*"
Condition:
StringEquals:
"kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com"
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${SchedulerFunctionName}"
Events:
SlackEvents:
Type: HttpApi
Properties:
Path: /slack/events
Method: POST
# --- Weekly Schedule Post (Monday 7am ET) ---
WeeklyPostFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: afterhours-weekly-post
Handler: src/weekly_post.handler
CodeUri: .
Environment:
Variables:
SHIFT_TABLE: !Ref ShiftTable
SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token
SHIFT_CHANNEL_PARAM: /afterhours-shift-manager/channel-id
SES_SENDER: noreply@seahaven.com
PAYROLL_RECIPIENTS: adam@seahaven.com
TZ: !Ref Timezone
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref ShiftTable
- Statement:
- Effect: Allow
Action:
- ssm:GetParameter
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/afterhours-shift-manager/*"
- Effect: Allow
Action:
- kms:Decrypt
Resource: "*"
Condition:
StringEquals:
"kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com"
- Effect: Allow
Action:
- ses:SendEmail
Resource:
- !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/seahaven.com"
Events:
# EST: 7am ET = 12:00 UTC (Nov-Mar)
WeeklyPostEST:
Type: Schedule
Properties:
Schedule: cron(0 12 ? * MON *)
Description: "Post weekly schedule Monday 7am EST"
Enabled: true
# EDT: 7am ET = 11:00 UTC (Mar-Nov)
WeeklyPostEDT:
Type: Schedule
Properties:
Schedule: cron(0 11 ? * MON *)
Description: "Post weekly schedule Monday 7am EDT"
Enabled: true
# --- Roster Sync Lambda (daily sync from 3CX) ---
RosterSyncFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: afterhours-roster-sync
Handler: src/roster_sync.handler
CodeUri: .
Timeout: 60
Environment:
Variables:
SHIFT_TABLE: !Ref ShiftTable
TCX_SSM_PREFIX: /3cx-scheduler
SYNC_GROUP: DEFAULT
TZ: !Ref Timezone
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref ShiftTable
- Statement:
- Effect: Allow
Action:
- ssm:GetParametersByPath
- ssm:GetParameter
- ssm:GetParameters
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/3cx-scheduler"
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/3cx-scheduler/*"
- Effect: Allow
Action:
- kms:Decrypt
Resource: "*"
Condition:
StringEquals:
"kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com"
Events:
# Daily at 6am ET (before the 7am schedule post and 8am 3CX scheduler)
# EST: 6am ET = 11:00 UTC (Nov-Mar)
RosterSyncEST:
Type: Schedule
Properties:
Schedule: cron(0 11 ? * * *)
Description: "Sync roster from 3CX at 6am EST"
Enabled: true
# EDT: 6am ET = 10:00 UTC (Mar-Nov)
RosterSyncEDT:
Type: Schedule
Properties:
Schedule: cron(0 10 ? * * *)
Description: "Sync roster from 3CX at 6am EDT"
Enabled: true
Outputs:
SlackBotApiUrl:
Description: URL for Slack app Request URL configuration
Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/slack/events"
ShiftTableName:
Value: !Ref ShiftTable
SlackBotFunctionArn:
Value: !GetAtt SlackBotFunction.Arn
RosterSyncFunctionArn:
Value: !GetAtt RosterSyncFunction.Arn