Step 4: Send Email Notification
Now we’ll add email sending to complete the workflow. We’ll use SendGrid as the main example, with alternatives for other services.
Email Service Options
Cloud-Native:
- AWS SES - Simple Email Service (included with AWS)
- Azure Logic Apps - Email connector (included with Azure)
- GCP - Use SendGrid or Mailgun (no native email service)
Third-Party:
- SendGrid - Popular, good free tier (100 emails/day)
- Mailgun - Good free tier (5,000 emails/month)
- Twilio SendGrid - Same as SendGrid
For this tutorial, we’ll show SendGrid as the main path, with AWS SES as an alternative.
Install Dependencies
First, install the email service SDK:
# For SendGrid
npm install @sendgrid/mail
# For AWS SES (if using AWS)
npm install @aws-sdk/client-ses
# For Mailgun
npm install mailgun.js form-data # For SendGrid
pip install sendgrid
# For AWS SES (if using AWS)
pip install boto3
# For Mailgun
pip install mailgun SendGrid Integration
Here’s how to send emails with SendGrid:
// handler.js - Complete with email sending
const sgMail = require('@sendgrid/mail');
// Set API key from environment variable
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
exports.handler = async (event) => {
try {
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
);
// Send email
await sendEmail(message);
console.log('Notification sent for:', objectKey);
}
return { statusCode: 200, body: 'Success' };
} catch (error) {
console.error('Error:', error);
return { statusCode: 500, body: error.message };
}
};
async function sendEmail(message) {
const msg = {
to: process.env.RECIPIENT_EMAIL,
from: process.env.SENDER_EMAIL,
subject: message.subject,
text: message.body,
html: `<p>${message.body.replace(/\n/g, '<br>')}</p>`
};
try {
await sgMail.send(msg);
console.log('Email sent successfully');
} catch (error) {
console.error('Email error:', error.response?.body);
throw error;
}
}
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 has been uploaded to your storage bucket.
File Details:
- Name: ${fileName}
- Path: ${objectKey}
- Size: ${sizeInMB} MB
- Bucket: ${bucketName}
- Uploaded: ${new Date(eventTime).toLocaleString()}
`
};
} # handler.py - Complete with email sending
import os
import json
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
def handler(event, context):
try:
# Initialize SendGrid
sg = SendGridAPIClient(os.environ['SENDGRID_API_KEY'])
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
)
# Send email
send_email(sg, message)
print(f'Notification sent for: {object_key}')
return {'statusCode': 200, 'body': 'Success'}
except Exception as error:
print(f'Error: {error}')
return {'statusCode': 500, 'body': str(error)}
def send_email(sg, message):
mail = Mail(
from_email=os.environ['SENDER_EMAIL'],
to_emails=os.environ['RECIPIENT_EMAIL'],
subject=message['subject'],
plain_text_content=message['body']
)
try:
response = sg.send(mail)
print(f'Email sent: {response.status_code}')
except Exception as error:
print(f'Email error: {error}')
raise
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 has been uploaded to your storage bucket.
File Details:
- Name: {file_name}
- Path: {object_key}
- Size: {size_in_mb:.2f} MB
- Bucket: {bucket_name}
- Uploaded: {event_time}
"""
} AWS SES Integration
If you’re using AWS, you can use SES instead:
// Using AWS SES
const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses');
const sesClient = new SESClient({ region: process.env.AWS_REGION });
async function sendEmail(message) {
const params = {
Source: process.env.SENDER_EMAIL,
Destination: {
ToAddresses: [process.env.RECIPIENT_EMAIL]
},
Message: {
Subject: { Data: message.subject },
Body: {
Text: { Data: message.body }
}
}
};
try {
const command = new SendEmailCommand(params);
const response = await sesClient.send(command);
console.log('Email sent:', response.MessageId);
} catch (error) {
console.error('Email error:', error);
throw error;
}
} # Using AWS SES
import boto3
from botocore.exceptions import ClientError
ses_client = boto3.client('ses', region_name=os.environ['AWS_REGION'])
def send_email(message):
try:
response = ses_client.send_email(
Source=os.environ['SENDER_EMAIL'],
Destination={'ToAddresses': [os.environ['RECIPIENT_EMAIL']]},
Message={
'Subject': {'Data': message['subject']},
'Body': {'Text': {'Data': message['body']}}
}
)
print(f'Email sent: {response['MessageId']}')
except ClientError as error:
print(f'Email error: {error}')
raise Environment Variables
Never hardcode API keys or email addresses. Use environment variables:
Configuration Table:
| Setting | Environment Variable | Example Value |
|---|---|---|
| SendGrid API Key | SENDGRID_API_KEY | SG.xxxxxxxxxxxxx |
| Sender Email | SENDER_EMAIL | notifications@example.com |
| Recipient Email | RECIPIENT_EMAIL | admin@example.com |
| AWS Region | AWS_REGION | us-east-1 |
Set Environment Variables
In AWS Lambda Console:
1. Go to your function
2. Click "Configuration" tab
3. Click "Environment variables"
4. Click "Edit"
5. Add variables:
- SENDGRID_API_KEY: your-api-key
- SENDER_EMAIL: sender@example.com
- RECIPIENT_EMAIL: recipient@example.com
6. Click "Save"
Or using AWS CLI:
aws lambda update-function-configuration \
--function-name notify-on-upload \
--environment Variables='{
SENDGRID_API_KEY=your-key,
SENDER_EMAIL=sender@example.com,
RECIPIENT_EMAIL=recipient@example.com
}' In Azure Portal:
1. Go to your Function App
2. Click "Configuration" in left menu
3. Click "+ New application setting"
4. Add each variable:
- SENDGRID_API_KEY
- SENDER_EMAIL
- RECIPIENT_EMAIL
5. Click "Save"
Or using Azure CLI:
az functionapp config appsettings set \
--name your-function-app \
--resource-group your-rg \
--settings \
SENDGRID_API_KEY=your-key \
SENDER_EMAIL=sender@example.com \
RECIPIENT_EMAIL=recipient@example.com In GCP Console:
1. Go to Cloud Functions
2. Click your function
3. Click "Edit"
4. Expand "Runtime, build, connections and security settings"
5. Click "Runtime environment variables"
6. Add variables:
- SENDGRID_API_KEY: your-key
- SENDER_EMAIL: sender@example.com
- RECIPIENT_EMAIL: recipient@example.com
7. Click "Deploy"
Or using gcloud CLI:
gcloud functions deploy notify-on-upload \
--set-env-vars \
SENDGRID_API_KEY=your-key,\
SENDER_EMAIL=sender@example.com,\
RECIPIENT_EMAIL=recipient@example.com Error Handling
Add retry logic and error logging:
async function sendEmail(message, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
await sgMail.send(msg);
return; // Success
} catch (error) {
if (i === retries - 1) {
// Last retry failed
console.error('Email failed after retries:', error);
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
Fill in the Blank
Complete this line to get the API key from environment variables:
// Node.js
const apiKey = process.env.___________;
// Python
api_key = os.environ['___________']
Answer
// Node.js
const apiKey = process.env.SENDGRID_API_KEY;
// Python
api_key = os.environ['SENDGRID_API_KEY']Verify Email Setup
Before deploying, test email sending:
- Verify sender email in SendGrid/Mailgun dashboard
- Test API key with a simple script
- Check email delivery in service dashboard
What’s Next?
Now that email sending is integrated, let’s test the complete workflow. In the next page, we’ll upload a file, verify the function runs, check logs, and confirm the email is sent.