Six months ago, I was deep in the trenches of SST v2, wrestling with its CloudFormation-based architecture and dealing with long deployment times. I wish I had known what I know now about SST v3 (codename SST Ion)—it would have saved me hours of frustration.
So, if my past self (or anyone else in the same boat) is reading this, here’s everything you need to know about why SST v3 is a game-changer and how it can make your life easier.
SST v2 vs. SST v3: What Changed?
SST v2 was built on AWS CDK and CloudFormation, meaning your entire infrastructure was defined within the SST ecosystem and compiled into CloudFormation templates. This came with limitations:
✅ Slower deployments due to CloudFormation stacks
✅ Vendor lock-in with AWS services
✅ Complex stack management
Then came SST v3, which introduced Pulumi and Terraform, eliminating CloudFormation stacks. The result?
🚀 Faster deployments—no more waiting on CloudFormation!
🌎 Multi-cloud support—Cloudflare, AWS, and more
🛠️ Custom components—Use Pulumi to extend SST however you like
This shift removes vendor lock-in and gives you more flexibility than ever.
Why SST v3 Makes Microservices Easier
Imagine you have several microservices for your application, each requiring its own resources like S3 buckets, DynamoDB tables, or Lambda functions. With SST v3, you define all those resources in code, making it easy to add new services, remove outdated ones, and scale dynamically.
Key Benefits for Microservices Architecture
✔️ Infrastructure as Code (IaC) – Define all cloud resources programmatically
✔️ No more YAML – No need to manually configure CloudFormation templates
✔️ Faster scaling – Add/remove services with confidence
✔️ Less time spent debugging deployments
Now, instead of fighting YAML files, you’ll spend more time actually building features.
Let’s Start Building: Serverless API with SST
In this tutorial, we’ll build a serverless API that does the following:
✅ Stores JSON data in DynamoDB when triggered
✅ Uses a DynamoDB stream to process new entries
✅ Sends an email notification based on the stored data
This demo will show how easy it is to integrate multiple AWS services using SST v3 and get your serverless application running efficiently.
Step 1: Set Up Your Project
First, create a new project directory and initialize it:
mkdir email-buddy-tracker && cd email-buddy-tracker
bun init -y
Now, initialize SST in your directory by running:
npx sst@latest init
SST Initialization Prompts
You’ll be prompted with a few options:
• Use SST templates? → Select default
• Choose a provider? → Pick AWS for this demo (feel free to experiment with others later)
After the setup, you’ll see a new file created sst.config.ts
This file acts as the root configuration for your SST project.
export default $config({
app(input) {
return {
name: "email-buddy-tracker",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "aws",
};
},
async run() {
// Define your infrastructure and logic here
},
});
Step 2: Add AWS Resources
To support our API, we’ll need to create a DynamoDB table, an email component, and an HTTP API using SST.
First, install the required dependencies:
bun add electrodb hono @aws-sdk/client-sesv2
- ElectroDB: Simplifies single-table design patterns for DynamoDB.
- Hono: A lightweight, high-performance web framework for building your Lambda-based API.
- AWS SDK: Interact with AWS services (SES, DynamoDB, etc.).
Update sst.config.ts
to Define Resources
Modify your sst.config.ts
to create necessary AWS components:
export default $config({
app(input) {
return {
name: "email-buddy-tracker",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "aws",
};
},
async run() {
// Define AWS resources
const userRecordsTable = new sst.aws.Dynamo("UserRecords", {
fields: {
pk: "string",
sk: "string"
},
primaryIndex: { hashKey: "pk", rangeKey: "sk" }
});
const email = new sst.aws.Email("MyEmail", {
sender: "martin@gizmodlabs.com",
});
new sst.aws.Function("HonoApi", {
url: true, // Enables public URL for API access
handler: "src/index.handler",
link: [userRecordsTable, email],
});
},
});
Step 3: Create the API Handler
Now, let’s create the root API handler using Hono.js, a lightweight and high-performance web framework.
Create a src
directory and add an index.ts
file:
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export const handler = handle(app)
With this setup, your API is ready to go!
With that setup you should be able to run sst dev
or add bun run dev
.
You should see something like this below
Step 4: Define a User Entity & a Dynamic Email Sender
Building scalable and efficient serverless applications requires a robust data model and a reliable email system. In this step, we'll leverage ElectroDB to define a User entity and use AWS SES for sending emails dynamically. This setup will ensure flexibility, maintainability, and scalability incase you want to take this further.
Defining the User Entity with ElectroDB
Instead of directly using the AWS SDK for DynamoDB, ElectroDB simplifies working with a single-table design. We’ll use SST resources to reference the table dynamically, eliminating hardcoded resource names and ensuring seamless deployment across different environments.
User Entity Code Implementation
import DynamoDB from "aws-sdk/clients/dynamodb";
import { Entity } from "electrodb";
import { Resource } from "sst"; // Dynamically reference the SST resource
const client = new DynamoDB.DocumentClient();
export const UserRecord = new Entity(
{
model: {
entity: "UserRecords",
version: "1",
service: "user",
},
attributes: {
email: { type: "string" },
firstName: { type: "string" },
lastName: { type: "string", required: true },
createdAt: { type: "string", default: () => new Date().toISOString() },
},
indexes: {},
},
{ client, table: Resource.UserRecords.name }
);
Configuring a Dynamic Email Sender
To avoid a static "from" email address, we'll use SST resources to reference it dynamically. Before sending emails, ensure that your domain or email is verified in AWS SES, or you’ll get stuck in the dreaded SES sandbox.
Email Sending Function
import { Resource } from "sst";
import { SESv2Client, SendEmailCommand, type SendEmailCommandOutput } from "@aws-sdk/client-sesv2";
const client = new SESv2Client();
export const sendEmail = (email: string, body: string): Promise<SendEmailCommandOutput> => {
return client.send(
new SendEmailCommand({
FromEmailAddress: Resource.MyEmail.sender, // Dynamically get sender email
Destination: { ToAddresses: [email] },
Content: {
Simple: {
Subject: { Data: "Hello from SST" },
Body: { Text: { Data: body } }
}
}
})
);
}
Step 5: Making Our API Functional
Now, let's wire everything together! Our API will handle POST requests, create a UserRecord in DynamoDB, and send a welcome email.
API Handler for User Creation & Email Sending
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
import { UserRecord } from './core/entity/user'
import { sendEmail } from './core/sendEmail'
const app = new Hono()
app.post('/', async (ctx) => {
const body = await ctx.req.json();
// Create a new user record in DynamoDB
const record = await UserRecord.create({
email: body.email,
firstName: body.firstName,
lastName: body.lastName,
createdAt: new Date().toISOString()
});
// Send a welcome email
const sentEmail = await sendEmail(body.email, "Welcome to SST and following my blog around SST!");
return ctx.json({
emailMessageId: sentEmail.MessageId || null,
dbRecord: record
});
});
export const handler = handle(app);
Testing the API
Run the service with:
bun run dev
# or
sst dev
Now, use Postman, Insomnia, or cURL to send a POST request to http://localhost:3000
with a sample payload:
{
"email": "martin@gizmodlabs.com",
"firstName": "John",
"lastName": "Doe"
}
If everything works correctly, you should receive:
✅ HTTP 200 Response
✅ SES MessageId confirming the email was sent
✅ New DynamoDB User Record
You can verify by checking your inbox and the DynamoDB console in AWS.
Final Thoughts: Is It Worth It?
Yes! By using SST, ElectroDB, and AWS SES, you gain:
- Scalability: Effortlessly handle more users without reworking the architecture.
- Maintainability: Dynamic references prevent hardcoded resource names, reducing errors with SST resources.
- Developer Experience: Faster iteration cycles with SST v3—no more CloudFormation headaches.
So, go ahead—build microservices, serverless apps, and AI-driven tools without the frustration of traditional cloud setups. And remember: if slow deployments make you cry, switch to SST v3—your sanity will thank you.
Example Repo: https://github.com/thisguymartin/email-buddy-tracker
Keep a eye out for more serverless content.
Top comments (0)