Intermediate 25 min

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

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()}
`
};
}

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;
}
}

Environment Variables

Never hardcode API keys or email addresses. Use environment variables:

Configuration Table:

SettingEnvironment VariableExample Value
SendGrid API KeySENDGRID_API_KEYSG.xxxxxxxxxxxxx
Sender EmailSENDER_EMAILnotifications@example.com
Recipient EmailRECIPIENT_EMAILadmin@example.com
AWS RegionAWS_REGIONus-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
}'

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:

  1. Verify sender email in SendGrid/Mailgun dashboard
  2. Test API key with a simple script
  3. 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.