Almost any web app with authenticated users will need to send some emails. You could work around it by using an authenticated service like Auth0 or AWS Incognito, but let’s assume you’ve set up Remix with a stack that ships with built-in authentication. Kent C. Dodds’ Epic Stack comes pre-wired to use Resend, and that’s a great option; however, my current client already has a large AWS infrastructure setup, so it makes sense to use Amazon’s Simple Email Service (SES).
The Plan
AWS publishes an SES package on NPM that I could use directly, but I’m not going to do that for two reasons:
1. Switching email services later will involve a major re-write
2. It’s hard to use
Nodemailer is the de-facto standard for sending emails from Node, and since Remix is basically React Router Node Edition™️, it will work with Remix. Nodemailer is going to handle all the tricky bits of proper email formatting. Combining Nodemailer with the SES adapter will be easier than making SES work on its own, and if we choose to switch from SES in the future, it will be quick to change.
AWS Credentials
You’ll need to add SESFullAccess permissions to your server’s IAM (Identity and Access Management, how AWS manages permissions). Setting up AWS is outside the scope of this guide. You are here because you have to use AWS, so ask the person who manages AWS for your organization. If you want the less complex mail-sending route, use Resend.
Packages to Install
Nodemailer: nodemailer
With millions of weekly downloads, this is a trusted email service for Node. It will do the heavy lifting of making sure our emails are correctly formatted for SES.
Nodemailer Types: @types/nodemailer
Assuming you are using Typescript. Leave this out if you are using JavaScript.
Client SES: @aws-sdk/client-ses
This package from the AWS SDK will help us tie into SES with minimal headache.
Credential Provider Node: @aws-sdk/credential-provider-node
Amazon has several ways of securely handling credentials, and this package handles all of them. It will look in your Node environment variables as well as other places on your server that your devops team might be looking to hide things. You don’t need this, but it does simplify things by letting your DevOps team select from multiple credential approaches without having to write new code.
npm i nodemailer @types/nodemailer @aws-sdk/client-ses @aws-sdk/credential-provider-node
Tangent: What if your email service isn’t set up yet?
As a React consultant, it’s common to have a client who knows what they want to build but doesn’t have a name and domain yet. You can’t send emails without a domain, but you need to send emails because transactional emails are critical for some flows, including authentication. You can’t wait to build authentication, so what do you do? Remix can interact with a database, and most stacks ship with Prisma, so you’ll need to build out a fake email system. It’s not much more than a table that holds a few email fields and a basic page to display all those emails. Build a sendEmail() function that takes all the arguments you’ll need for the real email service, but until that service is ready, I dump the data into the database. You’ll see it mixed in with my real email-sending function below, but first, here is the fake email system starting with the Prisma schema.
“`html
model Email {
id String @id @default(cuid())
to String
from String
subject String
replyTo String?
text String?
html String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
“`
Then, you can build your fake email database query, keeping it out of the loader and out of the client.
“`javascript
import { prisma } from “~/db.server”;
import type { Email } from “@prisma/client”;
export async function getAllMail(): Promise
return await prisma.email.findMany({
orderBy: { createdAt: “desc” },
take: 10, // in dev you probably only need the last few, adjust as necessary
});
}
“`
And now for your fake email page. Style as you see fit.
“`javascript
import { useLoaderData } from “@remix-run/react”;
import { getAllMail } from “~/services/mail/getAllMail”;
export async function loader() {
const mail = await getAllMail();
return { mail };
}
export default function MailPage() {
const { mail } = useLoaderData
return (
The Mail
{mail?.length ? (
mail.map((mail) => (
- To: {mail.to}
- From: {mail.from}
- Created at: {mail.createdAt}
- {mail.subject}
{mail.text}
) : (
)} ))
) : (
Sorry, no mail.
)}
);
}
“`
Now you have a page to view your fake emails, perfect for development. I like to add links to this page in places where you might need to check an email, such as after you create an account and need to verify your email address by clicking a link in an email.
“`javascript
{process.env.NODE_ENV === “development” && (
View dev emails
)}
“`
Let’s get back to the real email service!
Creating Your Email Method
First, we’ll create a type – you can skip this if you’re using JS. You’ll notice this matches the database table from the fake email system (if you chose to build that), but it’s also a common type that should work for most email services.
“`javascript
export type EmailType = {
to: string;
from: string;
replyTo?: string;
subject: string;
text?: string;
html?: string;
};
“`
Let’s build out our email-sending method. Your Remix code can end up in your client bundle, and if someone gets your email credentials, they can send spam from your account, which will result in all of your emails getting flagged by your user’s spam filters. Be sure to use server in the file name, like sendEmail.server.ts
“`javascript
import { prisma } from “~/db.server”;
import type { EmailType } from “./types”;
import nodemailer from “nodemailer”;
let aws = require(“@aws-sdk/client-ses”);
let { defaultProvider } = require(“@aws-sdk/credential-provider-node”);
/**
* Places the email into a database table for later review
* Use only for development
*/
async function sendFakeMail(email: EmailType) {
return prisma.fakeEmail.create({
data: email,
});
}
const ses = new aws.SES({
apiVersion: “2010-12-01”,
region: “us-east-1”,
defaultProvider,
});
// You could instead do something like this and skip the credential library
//
Source link