Skip to content

Commit f849738

Browse files
committed
Add Lambda Durable Functions - Human Approval Pattern
1 parent 492a348 commit f849738

File tree

7 files changed

+832
-0
lines changed

7 files changed

+832
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# Human-in-the-Loop Approval Workflow with Lambda Durable Functions
2+
3+
This pattern demonstrates a human-in-the-loop approval workflow using AWS Lambda Durable Functions. The workflow pauses execution for up to 24 hours while waiting for human approval via email, automatically handling timeouts and resuming when decisions are received.
4+
5+
**Important:** Lambda Durable Functions are currently available in the **us-east-2 (Ohio)** region only.
6+
7+
Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-durable-hitl-approval-sam
8+
9+
## Architecture
10+
11+
![Architecture Diagram](architecture.png)
12+
13+
The pattern uses Lambda Durable Functions to implement a cost-effective approval workflow that can wait up to 24 hours without incurring compute charges during the wait period.
14+
15+
### Workflow Steps
16+
17+
1. **User submits approval request** via API Gateway
18+
2. **Durable Function validates request** and creates a callback
19+
3. **UUID mapping stored in DynamoDB** (callback ID → UUID)
20+
4. **Email notification sent via SNS** with approve/reject links
21+
5. **Function pauses execution** (no compute charges during wait)
22+
6. **Approver clicks link** in email
23+
7. **Callback Handler retrieves callback ID** from DynamoDB
24+
8. **Callback sent to Lambda** using `SendDurableExecutionCallbackSuccess`
25+
9. **Durable Function resumes** and processes the decision
26+
27+
## Key Features
28+
29+
-**24-Hour Wait Time** - Can wait up to 24 hours for approval
30+
-**No Compute Charges During Wait** - Function suspended during wait period
31+
-**Email Notifications** - Sends approval requests with clickable links
32+
-**Short, Clean URLs** - Uses UUID instead of long callback IDs
33+
-**DynamoDB Mapping** - Stores UUID → callback ID mapping with TTL
34+
-**Automatic Timeout** - Auto-rejects after 24 hours
35+
-**HTML Response Pages** - Beautiful approval/rejection confirmation pages
36+
37+
## Prerequisites
38+
39+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
40+
* [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) installed
41+
* Python 3.14 runtime (automatically provided by Lambda)
42+
43+
## Deployment
44+
45+
1. Navigate to the pattern directory:
46+
```bash
47+
cd lambda-durable-hitl-approval-sam
48+
```
49+
50+
2. Build the SAM application:
51+
```bash
52+
sam build
53+
```
54+
55+
3. Deploy the application (must use us-east-2 region):
56+
```bash
57+
sam deploy --guided --region us-east-2
58+
```
59+
60+
During the guided deployment:
61+
- Stack Name: `lambda-durable-hitl-approval`
62+
- AWS Region: `us-east-2`
63+
- Parameter ApproverEmail: `your-email@example.com`
64+
- Confirm changes: `N`
65+
- Allow SAM CLI IAM role creation: `Y`
66+
- Disable rollback: `N`
67+
- Save arguments to config file: `Y`
68+
69+
4. **Confirm SNS subscription**: Check your email and click the confirmation link from AWS SNS
70+
71+
5. Note the `ApiEndpoint` from the outputs
72+
73+
## Testing
74+
75+
### Submit an Approval Request
76+
77+
```bash
78+
curl -X POST https://YOUR-API-ID.execute-api.us-east-2.amazonaws.com/prod/requests \
79+
-H "Content-Type: application/json" \
80+
-d '{
81+
"requestId": "REQ-001",
82+
"amount": 5000,
83+
"description": "New server purchase"
84+
}'
85+
```
86+
87+
Response:
88+
```json
89+
{
90+
"message": "Request accepted"
91+
}
92+
```
93+
94+
### Check Your Email
95+
96+
You'll receive an email with:
97+
- Request details (ID, amount, description)
98+
- **APPROVE** link
99+
- **REJECT** link
100+
- Expiration time (24 hours)
101+
102+
### Click Approve or Reject
103+
104+
Click one of the links in the email. You'll see a confirmation page with:
105+
- ✓ or ✗ icon
106+
- "Request Approved!" or "Request Rejected!" message
107+
- Confirmation that the workflow has been notified
108+
109+
### Monitor the Workflow
110+
111+
```bash
112+
# View durable function logs
113+
aws logs tail /aws/lambda/lambda-durable-hitl-approval-ApprovalFunction-XXXXX \
114+
--follow \
115+
--region us-east-2
116+
```
117+
118+
Look for:
119+
- `Starting approval workflow for request: REQ-001`
120+
- `Approval request sent. UUID: <uuid>`
121+
- `Approval workflow completed: approved` (or `rejected`)
122+
123+
## How It Works
124+
125+
### Callback Creation
126+
127+
The durable function creates a callback and stores the mapping:
128+
129+
```python
130+
from aws_durable_execution_sdk_python import DurableContext, durable_execution
131+
from aws_durable_execution_sdk_python.config import CallbackConfig, Duration
132+
133+
@durable_execution
134+
def lambda_handler(event, context: DurableContext):
135+
# Create callback with 24-hour timeout
136+
callback = context.create_callback(
137+
name='wait-for-approval',
138+
config=CallbackConfig(timeout=Duration.from_hours(24))
139+
)
140+
141+
# Generate short UUID and store mapping in DynamoDB
142+
request_uuid = str(uuid.uuid4())
143+
table.put_item(Item={
144+
'uuid': request_uuid,
145+
'callbackId': callback.callback_id,
146+
'ttl': int(time.time()) + 86400 # 24 hours
147+
})
148+
149+
# Send email with UUID-based URLs
150+
approve_url = f"{api_base_url}/approve/{request_uuid}"
151+
152+
# Wait for callback result
153+
approval = callback.result()
154+
```
155+
156+
### Callback Handler
157+
158+
When user clicks approve/reject:
159+
160+
```python
161+
def lambda_handler(event, context):
162+
# Get UUID from URL path
163+
request_uuid = event['pathParameters']['uuid']
164+
165+
# Fetch callback ID from DynamoDB
166+
response = table.get_item(Key={'uuid': request_uuid})
167+
callback_id = response['Item']['callbackId']
168+
169+
# Send callback to durable function
170+
lambda_client.send_durable_execution_callback_success(
171+
CallbackId=callback_id,
172+
Result=json.dumps({'decision': 'approved'})
173+
)
174+
```
175+
176+
### Cost Efficiency
177+
178+
**Traditional Lambda approach:**
179+
- Cannot wait more than 15 minutes (Lambda max timeout)
180+
- Would need Step Functions or polling mechanism
181+
- Complex state management
182+
183+
**Durable Functions approach:**
184+
- Wait up to 24 hours
185+
- No compute charges during wait
186+
- Simple, clean code
187+
- Automatic state management
188+
189+
## Configuration
190+
191+
### Adjust Timeout Duration
192+
193+
Modify in `template.yaml`:
194+
195+
```yaml
196+
DurableConfig:
197+
ExecutionTimeout: 86400 # 24 hours in seconds
198+
```
199+
200+
And in `src/lambda_function.py`:
201+
202+
```python
203+
config=CallbackConfig(timeout=Duration.from_hours(24))
204+
```
205+
206+
### Change Email Address
207+
208+
Update during deployment or redeploy with new email:
209+
210+
```bash
211+
sam deploy --parameter-overrides ApproverEmail=new-email@example.com
212+
```
213+
214+
## Use Cases
215+
216+
- **Expense Approvals** - Wait for manager approval on spending requests
217+
- **Document Reviews** - Pause workflow while documents are reviewed
218+
- **Deployment Approvals** - Require human approval before production deployments
219+
- **Access Requests** - Pause while security team reviews access requests
220+
- **Contract Approvals** - Wait for legal team approval on contracts
221+
222+
## Monitoring
223+
224+
### CloudWatch Logs
225+
226+
Monitor the durable function:
227+
```bash
228+
aws logs tail /aws/lambda/lambda-durable-hitl-approval-ApprovalFunction-XXXXX \
229+
--region us-east-2 \
230+
--follow
231+
```
232+
233+
Monitor the callback handler:
234+
```bash
235+
aws logs tail /aws/lambda/lambda-durable-hitl-approv-CallbackHandlerFunction-XXXXX \
236+
--region us-east-2 \
237+
--follow
238+
```
239+
240+
### DynamoDB Table
241+
242+
Check the callback mappings:
243+
```bash
244+
aws dynamodb scan \
245+
--table-name lambda-durable-hitl-approval-callbacks \
246+
--region us-east-2
247+
```
248+
249+
## Cleanup
250+
251+
```bash
252+
sam delete --stack-name lambda-durable-hitl-approval --region us-east-2
253+
```
254+
255+
256+
## Learn More
257+
258+
- [Lambda Durable Functions Documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
259+
- [Durable Execution SDK (Python)](https://github.com/aws/aws-durable-execution-sdk-python)
260+
- [Callback Operations](https://docs.aws.amazon.com/lambda/latest/dg/durable-callback.html)
261+
- [SendDurableExecutionCallbackSuccess API](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/send_durable_execution_callback_success.html)
262+
263+
---
264+
265+
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
266+
267+
SPDX-License-Identifier: MIT-0
118 KB
Loading
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
{
2+
"title": "Human-in-the-Loop Approval Workflow with Lambda Durable Functions",
3+
"description": "Approval workflow that pauses execution while waiting for human decisions, with automatic timeout handling and callback-based resumption",
4+
"language": "Python",
5+
"level": "300",
6+
"framework": "SAM",
7+
"services": {
8+
"from": "apigateway",
9+
"to": "lambda"
10+
},
11+
"introBox": {
12+
"headline": "How it works",
13+
"text": [
14+
"This pattern demonstrates a human-in-the-loop approval workflow using Lambda Durable Functions with callback operations.",
15+
"The workflow pauses execution using create_callback() while waiting for human approval, incurring no compute charges during the wait period.",
16+
"If no decision is received within 24 hours, the workflow automatically times out and rejects the request.",
17+
"When an approver submits a decision via API, the durable function resumes from its checkpoint and processes the result.",
18+
"The pattern uses DynamoDB to map short UUIDs to callback IDs for clean approval URLs, and SNS to send email notifications with approve/reject links."
19+
]
20+
},
21+
"gitHub": {
22+
"template": {
23+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-hitl-approval-sam",
24+
"templateURL": "serverless-patterns/lambda-durable-hitl-approval-sam",
25+
"projectFolder": "lambda-durable-hitl-approval-sam",
26+
"templateFile": "lambda-durable-hitl-approval-sam/template.yaml"
27+
}
28+
},
29+
"resources": {
30+
"bullets": [
31+
{
32+
"text": "Lambda Durable Functions Documentation",
33+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
34+
},
35+
{
36+
"text": "Durable Execution SDK for Python",
37+
"link": "https://github.com/aws/aws-durable-execution-sdk-python"
38+
},
39+
{
40+
"text": "Callback Operations",
41+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-callback.html"
42+
}
43+
]
44+
},
45+
"deploy": {
46+
"text": [
47+
"Note: Lambda Durable Functions are currently available in us-east-2 (Ohio) region only.",
48+
"sam build",
49+
"sam deploy --guided --region us-east-2"
50+
]
51+
},
52+
"testing": {
53+
"text": [
54+
"See README.md for comprehensive testing instructions including:",
55+
"- Creating approval requests",
56+
"- Submitting approval decisions",
57+
"- Testing timeout behavior",
58+
"- Monitoring durable executions"
59+
]
60+
},
61+
"cleanup": {
62+
"text": [
63+
"sam delete --region us-east-2"
64+
]
65+
},
66+
"patternArch": {
67+
"icon1": {
68+
"x": 20,
69+
"y": 50,
70+
"service": "apigateway",
71+
"label": "API Gateway"
72+
},
73+
"icon2": {
74+
"x": 50,
75+
"y": 50,
76+
"service": "lambda",
77+
"label": "Durable Approval Function"
78+
},
79+
"icon3": {
80+
"x": 80,
81+
"y": 30,
82+
"service": "dynamodb",
83+
"label": "DynamoDB"
84+
},
85+
"icon4": {
86+
"x": 80,
87+
"y": 70,
88+
"service": "sns",
89+
"label": "SNS"
90+
},
91+
"line1": {
92+
"from": "icon1",
93+
"to": "icon2"
94+
},
95+
"line2": {
96+
"from": "icon2",
97+
"to": "icon3"
98+
},
99+
"line3": {
100+
"from": "icon2",
101+
"to": "icon4"
102+
}
103+
},
104+
"authors": [
105+
{
106+
"name": "Abhishek Agawane",
107+
"image": "https://drive.google.com/file/d/1E-5koDaKEaMUtOctX32I9TLwfh3kgpAq/view?usp=drivesdk",
108+
"bio": "Abhishek Agawane is a Security Consultant at Amazon Web Services with more than 8 years of industry experience. He helps organizations architect resilient, secure, and efficient cloud environments, guiding them through complex challenges and large-scale infrastructure transformations. He has helped numerous organizations enhance their cloud operations through targeted optimizations, robust architectures, and best-practice implementations.",
109+
"linkedin": "https://www.linkedin.com/in/agawabhi/"
110+
}
111+
]
112+
}

0 commit comments

Comments
 (0)