Chapter 6: AWS SES API. Passwordless OAuth API. Mailchimp API.
Last updated April 2025.
The section below is a preview of SaaS Boilerplate Book. To read the full text, you will need to purchase the book.
In Chapter 6, you will start with the codebase in the 6-begin folder of our saas repo and end up with the codebase in the 6-end folder.
We will cover the following topics in this chapter:
- AWS SES API - EmailTemplate model, insertTemplates and getEmailTemplate methods - Adding getEmailTemplate and sendEmail to signInOrSignUpViaGoogle - sendEmail method - Setting up AWS SES and EMAIL_SUPPORT_FROM_ADDRESS - Testing AWS SES API
- Passwordless OAuth API - Configure passwordless - Configure passwordless token - Express routes for Passwordless API - signInOrSignUpByPasswordless method for User model - Testing Passwordless OAuth API
- Mailchimp API - callAPI method - addToMailchimp method - Adding addToMailchimp to signInOrSignUpViaGoogle and signInOrSignUpByPasswordless - Environmental variables for Mailchimp API - Testing Mailchimp API
In this chapter, we have three major features to add to our SaaS boilerplate: - the ability to send emails (AWS SES API) - allowing end users to sign up or log in using passwordless authentication (Passwordless OAuth API) - saving email addresses to a Mailchimp list (Mailchimp API)
We will not only discuss how to build these features in detail but also discuss why you might want these feature in your SaaS business.
AWS SES API link
One common feature of modern web applications is the ability to send emails. In Chapter 5, we added user authentication using Google OAuth. Nowadays, a typical internet user signs up in many web applications. It is a good idea to send a welcome email to a newly signed-up user. When an end user logs into our web application for the first time, our API server, in addition to creating a new User document in our database, should send a welcome email to this user.
Besides welcoming a new user, sending emails might be useful for other situations. By the end of this book, you will set up the following emails in your SaaS boilerplate: - welcome email sent by API server to a newly signed-up user (this section) - login email sent by API server to let a user log into our web application using the passwordless method (next section of this chapter) - invitation email sent by API server to invite a new user to join an existing team (Chapter 7) - newPost email sent by AWS Lambda function sendEmailForNewPost to notify a user about a new Discussion or new Post inside an existing Discussion
Your business may have more use cases for sending emails. For example, in our SaaS product Async, we use AWS SES API to let Async's users create a new Post by simply replying to a newPost email.
This is the high-level outline of how we want our welcome email to work: - A new end user clicks the login button on the login page of our web application on the browser - Some method in our API server retrieves an email template for a welcome email from our MongoDB database - Some other method if our API server takes all necessary parameters and passes them to an AWS SES API method that sends a request to the AWS SES server. - The AWS SES server sends a request with an email message to the end user's Email server - The newly signed-up end user can see our welcome email on the browser, inside the end user's inbox web application
To better understand req-res cycles, let's display the above description on a diagram:

This is how we want AWS SES API to work in more detail: - On the browser, a new end user clicks the LoginButton component on the Login page of our web application - This triggers an entire cascades of redirects, methods, and req-res cycles - Eventually, the static method signInOrSignUpViaGoogle of our User model gets called - Inside the signInOrSignUpViaGoogle method, we will call two methods: getEmailTemplate and sendEmail - getEmailTemplate will search for the welcome email template inside the emailtemplates collection of our MongoDB database - if getEmailTemplate successfully finds the welcome email template, it adds variable parameters to the template (in our case, the user's displayName) - next, the sendEmail method gets called with multiple arguments (email template is one of those arguments) - sendEmail calls the ses.sendEmail AWS SES API method:
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SES.html#sendEmail-property
ses.sendEmailsends a request with thePOST methodfrom ourAPIserver to the AWS SES server - The AWS SES server sends an email to an Email server. In our case, it is Gmail server. - A newly signed-up user finds our welcome email inside their inbox web application on the browser. In our case, we will be checking our Gmail inbox on the browser.
In the next few subsection of this section, we will implement every step from the above detailed description.
EmailTemplate model, insertTemplates and getEmailTemplate methods link
In this section, we will define a new Mongoose model: EmailTemplate. At this point, our MongoDB database has two collections: users and sessions. We defined our User model in book/6-begin/api/server/models/User.ts, and that is why we have a users collection. The express-session package creates a sessions collection without us explicitly defining a schema and model for Session.
No need for us to reinvent the wheel. Let's open our book/6-begin/api/server/models/User.ts file and visually ignore the part that defines static methods and types for static methods:
import * as mongoose from 'mongoose';
const mongoSchema = new mongoose.Schema({
slug: {
type: String,
required: true,
unique: true,
},
createdAt: {
type: Date,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
displayName: String,
avatarUrl: String,
googleId: {
type: String,
unique: true,
sparse: true,
},
googleToken: {
accessToken: String,
refreshToken: String,
},
isSignedupViaGoogle: {
type: Boolean,
required: true,
default: false,
},
});
export interface UserDocument extends mongoose.Document {
slug: string;
createdAt: Date;
email: string;
displayName: string;
avatarUrl: string;
}
// static methods
const User = mongoose.model<UserDocument, UserModel>('User', mongoSchema);
export default User;As you can see from above, besides defining static methods and types, we: - defined mongoSchema - defined and exported UserDocument document - defined and exported User model
Since our EmailTemplate model will have no static methods, there is no reason for us to define EmailTemplateClass and there is no reason for us to call:
mongoSchema.loadClass(EmailTemplateClass);So let's only achieve the following three goals for our EmailTemplate model: - define mongoSchema - define and export EmailTemplateDocument document - define and export EmailTemplate model
Create a new file book/6-begin/api/server/models/EmailTemplate.ts and add the following content to it:
import * as _ from 'lodash';
import * as mongoose from 'mongoose';
const mongoSchema = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true,
},
subject: {
type: String,
required: true,
},
message: {
type: String,
required: true,
},
});
interface EmailTemplateDocument extends mongoose.Document {
name: string;
subject: string;
message: string;
}
const EmailTemplate = mongoose.model<EmailTemplateDocument>('EmailTemplate', mongoSchema);The EmailTemplate document in our database and the corresponding JS object on our API server will have only three parameters, all of them with the string data type and a unique name parameter.
In this book, we hardcode email templates into the code. When you start your API server (either locally or in production), our goal is to make the API server insert these hardcoded templates into our MongoDB database. We need to define a method to do this. Here is how we define an insertTemplates method:
async function insertTemplates() {
const templates = [
{
name: 'welcome',
subject: 'Welcome to SaaS by Async',
message: `<%= userName %>,
<p>
Thanks for signing up on our <a href="https://github.com/async-labs/saas" target="blank">SaaS boilerplate</a>!
</p>
<p>
If you are learning how to build a SaaS web app, check out our two books:
<a href="https://builderbook.org" target="blank">Builder Book</a>
and
<a href="https://builderbook.org/book" target="blank">SaaS Boilerplate</a>.
</p>
<p>
Also check out
<a href="https://async-await.com" target="blank"> Async</a>
, our communication tool for small teams of software developers.
</p>
Kelly & Timur, Team Async
`,
},
];
for (const t of templates) {
const et = await EmailTemplate.findOne({ name: t.name });
const message = t.message
.replace(/\n/g, '')
.replace(/[ ]+/g, ' ')
.trim();
if (!et) {
EmailTemplate.create(Object.assign({}, t, { message }));
} else if (et.subject !== t.subject || et.message !== message) {
EmailTemplate.updateOne({ _id: et._id }, { $set: { message, subject: t.subject } }).exec();
}
}
}As you can see, templates is an array of objects. Currently, this array has only one object that corresponds to the welcome email. These objects from the templates array are used to create corresponding MongoDB documents in our database. Eventually, by the end of this book, you will have a total of four templates with the following names: welcome, login, invitation, and newPost.
You are already familiar with how if..else syntax works from Chapter 4 and Chapter 5. In the above code, we use if...else like this:
if (!et) {
EmailTemplate.create(Object.assign({}, t, { message }));
} else if (et.subject !== t.subject || et.message !== message) {
EmailTemplate.updateOne({ _id: et._id }, { $set: { message, subject: t.subject } }).exec();
}You've reached the end of the Chapter 6 preview. To continue reading, you will need to purchase the book.
Last updated April 2025.