Step 2: Write the Serverless Function
This is the core of our system. The function receives events when files are uploaded and processes them.
Understanding Event Payloads
When a file is uploaded, the storage service sends an event to your function. The event contains information about the file.
Here’s what a typical event looks like:
Function Responsibilities
Your function needs to:
- Receive the event - Handler function gets called with event data
- Extract file metadata - Get bucket name, file name, size
- Format message - Create email content with file details
- Send notification - Call email service (we’ll add this in next page)
Function Skeleton
Let’s start with a basic function that just logs the event:
// handler.js
exports.handler = async (event) => {
console.log('Event received:', JSON.stringify(event, null, 2));
// Process each record in the event
for (const record of event.Records) {
const bucketName = record.s3.bucket.name;
const objectKey = record.s3.object.key;
const fileSize = record.s3.object.size;
console.log(`File uploaded: ${objectKey} in bucket ${bucketName}`);
console.log(`File size: ${fileSize} bytes`);
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'Event processed' })
};
}; # handler.py
import json
def handler(event, context):
print('Event received:', json.dumps(event, indent=2))
# Process each record in the event
for record in event.get('Records', []):
bucket_name = record['s3']['bucket']['name']
object_key = record['s3']['object']['key']
file_size = record['s3']['object']['size']
print(f'File uploaded: {object_key} in bucket {bucket_name}')
print(f'File size: {file_size} bytes')
return {
'statusCode': 200,
'body': json.dumps({'message': 'Event processed'})
} Parsing the Event
Different cloud providers send events in different formats. Let’s handle each:
// AWS S3 event structure
exports.handler = async (event) => {
for (const record of event.Records) {
// Extract file information
const bucketName = record.s3.bucket.name;
const objectKey = decodeURIComponent(
record.s3.object.key.replace(/\+/g, ' ')
);
const fileSize = record.s3.object.size;
const eventTime = record.eventTime;
// Get file extension
const fileExtension = objectKey.split('.').pop() || '';
console.log(`Bucket: ${bucketName}`);
console.log(`File: ${objectKey}`);
console.log(`Size: ${fileSize} bytes`);
console.log(`Type: ${fileExtension}`);
console.log(`Time: ${eventTime}`);
}
return { statusCode: 200 };
}; // Azure Blob Storage trigger
module.exports = async function (context, blobTrigger) {
// Azure provides blob name and URI directly
const blobName = context.bindingData.name;
const blobUri = context.bindingData.uri;
const blobSize = context.bindingData.properties.contentLength;
context.log(`Blob name: ${blobName}`);
context.log(`Blob URI: ${blobUri}`);
context.log(`Blob size: ${blobSize} bytes`);
// Extract container name from URI
const containerName = blobUri.split('/').slice(-2, -1)[0];
context.log(`Container: ${containerName}`);
return { status: 200 };
}; // GCP Cloud Storage trigger
exports.notifyOnUpload = (file, context) => {
// GCP provides file object directly
const bucketName = file.bucket;
const fileName = file.name;
const fileSize = file.size;
const contentType = file.contentType;
const timeCreated = file.timeCreated;
console.log(`Bucket: ${bucketName}`);
console.log(`File: ${fileName}`);
console.log(`Size: ${fileSize} bytes`);
console.log(`Type: ${contentType}`);
console.log(`Created: ${timeCreated}`);
return { status: 'success' };
}; Formatting the Message
Now let’s create a formatted message for the email:
// Format email message
function formatEmailMessage(bucketName, objectKey, fileSize, eventTime) {
const fileName = objectKey.split('/').pop();
const fileExtension = fileName.split('.').pop() || 'unknown';
const sizeInMB = (fileSize / (1024 * 1024)).toFixed(2);
const subject = `New file uploaded: ${fileName}`;
const body = `
A new file has been uploaded to your storage bucket.
File Details:
- Name: ${fileName}
- Path: ${objectKey}
- Size: ${sizeInMB} MB (${fileSize} bytes)
- Type: ${fileExtension}
- Bucket: ${bucketName}
- Uploaded: ${new Date(eventTime).toLocaleString()}
You can access this file in your storage bucket.
`;
return { subject, body };
}
// Usage
const message = formatEmailMessage(
'my-notifications-bucket-2025',
'documents/report.pdf',
1024000,
'2025-11-23T10:00:00.000Z'
);
console.log('Subject:', message.subject);
console.log('Body:', message.body); # Format email message
def format_email_message(bucket_name, object_key, file_size, event_time):
file_name = object_key.split('/')[-1]
file_extension = file_name.split('.')[-1] if '.' in file_name else 'unknown'
size_in_mb = file_size / (1024 * 1024)
subject = f'New file uploaded: {file_name}'
body = f"""
A new file has been uploaded to your storage bucket.
File Details:
- Name: {file_name}
- Path: {object_key}
- Size: {size_in_mb:.2f} MB ({file_size} bytes)
- Type: {file_extension}
- Bucket: {bucket_name}
- Uploaded: {event_time}
You can access this file in your storage bucket.
"""
return {'subject': subject, 'body': body}
# Usage
message = format_email_message(
'my-notifications-bucket-2025',
'documents/report.pdf',
1024000,
'2025-11-23T10:00:00.000Z'
)
print('Subject:', message['subject'])
print('Body:', message['body']) Complete Function (Before Email Integration)
Here’s the complete function structure. We’ll add email sending in the next page:
// handler.js - Complete function
exports.handler = async (event) => {
console.log('Event received:', JSON.stringify(event, null, 2));
try {
// Process each record
for (const record of event.Records) {
const bucketName = record.s3.bucket.name;
const objectKey = decodeURIComponent(
record.s3.object.key.replace(/\+/g, ' ')
);
const fileSize = record.s3.object.size;
const eventTime = record.eventTime;
// Format message
const message = formatEmailMessage(
bucketName,
objectKey,
fileSize,
eventTime
);
console.log('Message prepared:', message.subject);
// TODO: Send email (next page)
// await sendEmail(message);
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'Processed successfully' })
};
} catch (error) {
console.error('Error processing event:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
function formatEmailMessage(bucketName, objectKey, fileSize, eventTime) {
const fileName = objectKey.split('/').pop();
const sizeInMB = (fileSize / (1024 * 1024)).toFixed(2);
return {
subject: `New file uploaded: ${fileName}`,
body: `A new file (${fileName}, ${sizeInMB} MB) was uploaded to ${bucketName}.`
};
} # handler.py - Complete function
import json
def handler(event, context):
print('Event received:', json.dumps(event, indent=2))
try:
# Process each record
for record in event.get('Records', []):
bucket_name = record['s3']['bucket']['name']
object_key = record['s3']['object']['key']
file_size = record['s3']['object']['size']
event_time = record['eventTime']
# Format message
message = format_email_message(
bucket_name,
object_key,
file_size,
event_time
)
print('Message prepared:', message['subject'])
# TODO: Send email (next page)
# send_email(message)
return {
'statusCode': 200,
'body': json.dumps({'message': 'Processed successfully'})
}
except Exception as error:
print('Error processing event:', error)
return {
'statusCode': 500,
'body': json.dumps({'error': str(error)})
}
def format_email_message(bucket_name, object_key, file_size, event_time):
file_name = object_key.split('/')[-1]
size_in_mb = file_size / (1024 * 1024)
return {
'subject': f'New file uploaded: {file_name}',
'body': f'A new file ({file_name}, {size_in_mb:.2f} MB) was uploaded to {bucket_name}.'
} Try It: Predict the Output
Look at this event payload. What will record.s3.object.key contain?
{
"Records": [{
"s3": {
"bucket": { "name": "my-bucket" },
"object": { "key": "documents%2Freport.pdf", "size": 1024000 }
}
}]
}
Answer
The key is URL-encoded: documents%2Freport.pdf which decodes to documents/report.pdf.
The %2F is the encoded form of /. That’s why we use decodeURIComponent() in the code.
Common Bugs to Avoid
Bug 1: Wrong property path
// ❌ Wrong
const fileName = event.s3.object.key;
// ✅ Correct
const fileName = event.Records[0].s3.object.key;
Bug 2: Forgetting URL decoding
// ❌ Wrong (if key has special characters)
const objectKey = record.s3.object.key;
// ✅ Correct
const objectKey = decodeURIComponent(
record.s3.object.key.replace(/\+/g, ' ')
);
Bug 3: Not handling multiple records
// ❌ Wrong (only processes first record)
const record = event.Records[0];
// ✅ Correct (processes all records)
for (const record of event.Records) {
// process each
}
What’s Next?
You now have a function that receives events and formats messages. In the next page, we’ll add email sending functionality and connect everything together.