Skip to content
Swizec Teller - a geek with a hatswizec.com

Send daily random photos from an S3 bucket using AWS Lambda – CodeWithSwiz 25

We did it! We finished the experiment from a few weeks ago and The Girl can stop making jokes that I stole her present 🎉

Here's the plan:

  1. Buy cute IoT gimmick for Valentine's Day ✅
  2. Reverse engineer its API
  3. Build serverless Lambda that sends a picture ✅👆
  4. Move secrets to secrets manager ⌛️
  5. Read photos from S3 ⌛️
  6. Send base64 encoded photos ⌛️
  7. Run daily ⌛️

The Girl was getting upset with me [name|Friend]. Waiting for those last 3 steps since February while I focused on Serverless Handbook.

She waits no more! It's live

Lambda triggered on a daily cron
Lambda triggered on a daily cron

You can see the full code on GitHub. Here's what we built:

Serverless Handbook

Learn everything you need to dive into modern backend. Understand any backend

these chapters gave me knowledge which is sufficient to build real products and the hand-drawn diagrams and high-leveled descriptions gave me the feeling that i don't have any critical "knowledge gaps"

Launching soon, be the first to know.

Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

Secrets in secrets manager

Never deploy code with secret API keys in the files. You will get hacked and your data will get leaked.

And when we're talking anonymous strangers from the internet can send dick pics to your girl ... nu-uh. All identifying info and API tokens went into the AWS Secrets Manager.

Accessed in code like this:

// src/secrets.ts
import { SecretsManager } from "aws-sdk";
const ssm = new SecretsManager({
region: "us-east-1", // make sure this matches your region
});
export async function getBearerToken() {
const secret = await ssm
.getSecretValue({ SecretId: "loveboxBearerToken" })
.promise();
const { BEARER_TOKEN } = JSON.parse(secret?.SecretString || "");
return BEARER_TOKEN;
}

Grab the SecretsManager, instantiate with our region, write helper function to grab the API token. It talks to secrets secrets manager, retrieves the loveboxBearerToken secret, parses the JSON response and returns what we need.

I added my other secrets later.

And we remembered to give our Lambda permissions to read secrets:

# serverless.yml
provider:
# ...
iamRoleStatements:
- Effect: "Allow"
Action:
- "secretsmanager:GetSecretValue"
Resource: "arn:aws:secretsmanager:${self:provider.region}:*"

Borrowed all this from the Serverless Handbook chapter on secrets. No point in memorizing what you can look up 😇

Using secrets for the GraphQLClient

We need the bearer token to authorize our GraphQL client with the API. Means we have to instantiate with an async function.

But talking to SecretsManager is an API call itself and that could make our Lambda slow. The answer is memoization.

let graphQLClient: null | GraphQLClient = null;
async function createGraphQLClient() {
if (graphQLClient === null) {
const token = await getBearerToken();
graphQLClient = new GraphQLClient(
"https://app-api.loveboxlove.com/v1/graphql",
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
}
return graphQLClient;
}

We create a global variable graphQLClient. When it's defined createGraphQLClient returns the client, when it isn't it first instantiates our client after reading the token from secrets.

This lets us skip the secrets call when our Lambda gets reused ✌️

Read photos from S3

Reading photos from S3 happens in two steps:

  1. Get list of photos in bucket
  2. Read random photo

List photos

We start by giving our lambda permissions to read S3.

# serverless.yml
provider:
# ...
iamRoleStatements:
# ...
- Effect: "Allow"
Action:
- "s3:listObjects"
Resource: "arn:aws:s3:::lovebox-stash/*"

Yes, the S3 bucket is called lovebox-stash.

Then we create a helper function that grabs a list of objects from S3. This part was uneventful, but we had to make sure it works.

// src/pictures.ts
import { S3 } from "aws-sdk";
const s3 = new S3({
apiVersion: "2006-03-01",
});
async function listPictures() {
const list = await s3
.listObjects({
Bucket: "lovebox-stash",
})
.promise();
return list.Contents;
}

Instantiate an S3 client, keep it shared between Lambda calls, make a request to S3 to list every object in the bucket.

Read random photo

Grabbing a random photo is a matter of maths and fetching an S3 object. Like this:

// src/pictures.ts
export async function getPicture() {
const pictures = await listPictures();
if (pictures) {
const randomPic = pictures[Math.floor(Math.random() * pictures.length)];
if (randomPic.Key) {
const imageData = await s3
.getObject({
Bucket: "lovebox-stash",
Key: randomPic.Key,
})
.promise();
return imageData;
}
}
return null;
}

Grab list of photos, pick at random, read data. Ifs help with error handling.

Send base64 encoded photos

The final sendPicture function looks like this:

async function sendPicture(picture: S3.GetObjectOutput) {
const base64 = `data:${picture.ContentType};base64,${picture.Body?.toString(
"base64"
)}`;
const client = await createGraphQLClient();
const { recipient, deviceId } = await getRecipient();
const res = await client.request(
// query,
{
base64,
recipient,
contentType: ["Image"],
options: {
framesBase64: null,
deviceId,
},
}
);
console.log(res);
}

Get random photo, encode in base64, add the mime type. Create authorized GraphQL client, get recipient secrets, send as variables to the GraphQL query.

And you get a happy Swiz

Yay the hackery works

Run daily

This part's easy 👉 add cron config to serverless.yml

# serverless.yml
functions:
sendNote:
handler: dist/lovebox.sendNote
events:
- schedule: rate(1 day)

Run daily, at a time of your choosing.

Cheers,
~Swizec

PS: the Serverless Handbook paperback is now \\$36 on Amazon, not sure why but check it out if you're curious

Did you enjoy this article?

Published on April 6th, 2021 in

Serverless Handbook launching soon

Leave your email and be among the first to know when Serverless Handbook launches.

The perfect resource that shows you how to jump into modern backend and understand any backend. A why and how book.

Serverless Handbook

Learn everything you need to dive into modern backend. Understand any backend

these chapters gave me knowledge which is sufficient to build real products and the hand-drawn diagrams and high-leveled descriptions gave me the feeling that i don't have any critical "knowledge gaps"

Launching soon, be the first to know.

Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

Ready to Stop copy pasting D3 examples and create data visualizations of your own?  Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️