Amazon SES API support for email

Hi there, I'm implementing a saas application with WASP that I'll deploy in AWS. I want to use Amazon SES for emails. I know that I can use SMTP with WASP to send emails using SES, but there are some limitations with SES SMTP. I also know that SES API integration is not there out of the box. So, the question is, can I somehow override the email sending functionality and implement the SES API myself? I would love to add this feature back to the framework as well, but for now, I'd need a way to be able to use custom code for email sending without disrupting anything else. Thanks
8 Replies
kapa.ai
kapa.ai3d ago
Hi @saqib9810. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the knowledge sources, Wasp currently supports several email providers out of the box: Dummy (for development only), SMTP, Mailgun, and SendGrid. While Amazon SES API is not directly supported, there are a couple of approaches you could consider. Current Options You've correctly identified that you can use SMTP with Amazon SES, as Wasp supports SMTP providers. As mentioned in the documentation:
Many transactional email providers (e.g. Mailgun, SendGrid but also others) can also use SMTP, so you can use them as well. Sending Emails > Providers
Custom Implementation Options The knowledge sources don't explicitly mention a way to override the email sending functionality with a custom implementation. However, there are a couple of issues and discussions that suggest this is something the Wasp team is considering: 1. There's an open issue about allowing provider-specific options for email senders: Allow specifying provider specific options, like templateId for Sendgrid 2. Another open issue discusses introducing a templating system for emails: Introduce templating system for writing emails in Wasp Potential Workaround While not ideal, you could potentially: 1. Use the SMTP provider configuration in Wasp 2. Implement your own action/function that uses the AWS SDK for SES directly 3. Use your custom function instead of the built-in emailSender.send() method This approach would bypass Wasp's email sending infrastructure but would allow you to use the SES API directly. Contributing Back Your interest in contributing this feature back to the framework is valuable. The Wasp team seems open to expanding email functionality based on the open issues. You might want to: 1. Create a new issue specifically for Amazon SES API support 2. Potentially submit a pull request with your implementation The knowledge sources don't provide enough information about how to directly override the email sending functionality without disrupting other parts of the system. For a more definitive answer, you might want to ask in the Wasp Discord community or create a GitHub issue to discuss this specific use case with the Wasp team. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @saqib9810, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
saqib9810
saqib9810OP3d ago
@kapa.ai Can you please expand on the workaround and give a code sample for it? I'm using the email auth. How/where do I use my custom email sending code?
kapa.ai
kapa.ai3d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I'll expand on the workaround for implementing Amazon SES API with Wasp's email authentication system. Workaround Implementation Since you're using email authentication, you'll need to create a custom sign-up action that uses Amazon SES API instead of Wasp's built-in email sender. Here's how you could implement this: 1. First, install the AWS SDK for SES:
npm install @aws-sdk/client-ses
npm install @aws-sdk/client-ses
2. Create a custom sign-up action by overriding the default one. Wasp provides helper functions that you can use while implementing your own email sending logic:
// src/auth/signup.js
import {
ensurePasswordIsPresent,
ensureValidPassword,
ensureValidEmail,
createProviderId,
sanitizeAndSerializeProviderData,
findAuthIdentity,
createUser,
createEmailVerificationLink,
// We won't use this, but will implement our own
// sendEmailVerificationEmail,
} from 'wasp/server/auth'
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";

// Configure the AWS SES client
const sesClient = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}
});

// Custom function to send email via SES API
async function sendEmailViaSES(to, subject, text, html) {
const params = {
Source: process.env.SES_FROM_EMAIL,
Destination: {
ToAddresses: [to],
},
Message: {
Subject: {
Data: subject,
},
Body: {
Text: {
Data: text,
},
Html: {
Data: html,
},
},
},
};

try {
const command = new SendEmailCommand(params);
const response = await sesClient.send(command);
return response;
} catch (error) {
console.error("Error sending email via SES:", error);
throw error;
}
}
export const signup = async (args, _context) => {
ensureValidEmail(args)
ensurePasswordIsPresent(args)
ensureValidPassword(args)

try {
const providerId = createProviderId('email', args.email)
const existingAuthIdentity = await findAuthIdentity(providerId)

if (existingAuthIdentity) {
// Handle existing user case
} else {
// sanitizeAndSerializeProviderData will hash the user's password
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
isEmailVerified: false,
emailVerificationSentAt: null,
passwordResetSentAt: null,
})

await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)

// Verification link links to a client route e.g. /email-verification
const verificationLink = await createEmailVerificationLink(args.email, '/email-verification');

try {
// Use our custom SES function instead of Wasp's sendEmailVerificationEmail
await sendEmailViaSES(
args.email,
"Verify your email",
`Click the link below to verify your email: ${verificationLink}`,
`
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`
);
} catch (e) {
console.error("Failed to send email verification email:", e);
throw new Error("Failed to send email verification email.");
}
}

return {
success: true,
message: "Signup successful! Please check your email to verify your account."
};
} catch (error) {
return {
success: false,
message: error.message
};
}
}
// src/auth/signup.js
import {
ensurePasswordIsPresent,
ensureValidPassword,
ensureValidEmail,
createProviderId,
sanitizeAndSerializeProviderData,
findAuthIdentity,
createUser,
createEmailVerificationLink,
// We won't use this, but will implement our own
// sendEmailVerificationEmail,
} from 'wasp/server/auth'
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";

// Configure the AWS SES client
const sesClient = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}
});

// Custom function to send email via SES API
async function sendEmailViaSES(to, subject, text, html) {
const params = {
Source: process.env.SES_FROM_EMAIL,
Destination: {
ToAddresses: [to],
},
Message: {
Subject: {
Data: subject,
},
Body: {
Text: {
Data: text,
},
Html: {
Data: html,
},
},
},
};

try {
const command = new SendEmailCommand(params);
const response = await sesClient.send(command);
return response;
} catch (error) {
console.error("Error sending email via SES:", error);
throw error;
}
}
export const signup = async (args, _context) => {
ensureValidEmail(args)
ensurePasswordIsPresent(args)
ensureValidPassword(args)

try {
const providerId = createProviderId('email', args.email)
const existingAuthIdentity = await findAuthIdentity(providerId)

if (existingAuthIdentity) {
// Handle existing user case
} else {
// sanitizeAndSerializeProviderData will hash the user's password
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
isEmailVerified: false,
emailVerificationSentAt: null,
passwordResetSentAt: null,
})

await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)

// Verification link links to a client route e.g. /email-verification
const verificationLink = await createEmailVerificationLink(args.email, '/email-verification');

try {
// Use our custom SES function instead of Wasp's sendEmailVerificationEmail
await sendEmailViaSES(
args.email,
"Verify your email",
`Click the link below to verify your email: ${verificationLink}`,
`
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`
);
} catch (e) {
console.error("Failed to send email verification email:", e);
throw new Error("Failed to send email verification email.");
}
}

return {
success: true,
message: "Signup successful! Please check your email to verify your account."
};
} catch (error) {
return {
success: false,
message: error.message
};
}
}
3. Make sure to define this custom action in your main.wasp file:
action signup {
fn: import { signup } from "@server/auth/signup.js",
entities: [User]
}

auth {
userEntity: User,
methods: {
email: {
fromField: {
name: "My App",
},
emailVerification: {
clientRoute: EmailVerificationRoute,
getEmailContentFn: import { getVerificationEmailContent } from "@server/auth/email.js",
},
passwordReset: {
clientRoute: PasswordResetRoute,
getEmailContentFn: import { getPasswordResetEmailContent } from "@server/auth/email.js",
},
// Use your custom signup action
signup: {
action: signup
}
}
}
}
action signup {
fn: import { signup } from "@server/auth/signup.js",
entities: [User]
}

auth {
userEntity: User,
methods: {
email: {
fromField: {
name: "My App",
},
emailVerification: {
clientRoute: EmailVerificationRoute,
getEmailContentFn: import { getVerificationEmailContent } from "@server/auth/email.js",
},
passwordReset: {
clientRoute: PasswordResetRoute,
getEmailContentFn: import { getPasswordResetEmailContent } from "@server/auth/email.js",
},
// Use your custom signup action
signup: {
action: signup
}
}
}
}
4. You'll also need to override the password reset functionality in a similar way:
// src/auth/email.js
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";

// Configure the AWS SES client (same as in signup.js)
const sesClient = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}
});

// Custom function to send email via SES API (same as in signup.js)
async function sendEmailViaSES(to, subject, text, html) {
// Same implementation as above
}
export const getVerificationEmailContent = async (verificationLink) => {
return {
subject: "Verify your email",
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`,
// This is where you'd normally use Wasp's emailSender, but we'll override it
send: async (to) => {
return sendEmailViaSES(
to,
"Verify your email",
`Click the link below to verify your email: ${verificationLink}`,
`
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`
);
}
};
};

export const getPasswordResetEmailContent = async (passwordResetLink) => {
return {
subject: "Reset your password",
text: `Click the link below to reset your password: ${passwordResetLink}`,
html: `
<p>Click the link below to reset your password</p>
<a href="${passwordResetLink}">Reset password</a>
`,
// Override the send method
send: async (to) => {
return sendEmailViaSES(
to,
"Reset your password",
`Click the link below to reset your password: ${passwordResetLink}`,
`
<p>Click the link below to reset your password</p>
<a href="${passwordResetLink}">Reset password</a>
`
);
}
};
};
// src/auth/email.js
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";

// Configure the AWS SES client (same as in signup.js)
const sesClient = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}
});

// Custom function to send email via SES API (same as in signup.js)
async function sendEmailViaSES(to, subject, text, html) {
// Same implementation as above
}
export const getVerificationEmailContent = async (verificationLink) => {
return {
subject: "Verify your email",
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`,
// This is where you'd normally use Wasp's emailSender, but we'll override it
send: async (to) => {
return sendEmailViaSES(
to,
"Verify your email",
`Click the link below to verify your email: ${verificationLink}`,
`
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`
);
}
};
};

export const getPasswordResetEmailContent = async (passwordResetLink) => {
return {
subject: "Reset your password",
text: `Click the link below to reset your password: ${passwordResetLink}`,
html: `
<p>Click the link below to reset your password</p>
<a href="${passwordResetLink}">Reset password</a>
`,
// Override the send method
send: async (to) => {
return sendEmailViaSES(
to,
"Reset your password",
`Click the link below to reset your password: ${passwordResetLink}`,
`
<p>Click the link below to reset your password</p>
<a href="${passwordResetLink}">Reset password</a>
`
);
}
};
};
5. Don't forget to set up the necessary environment variables in your .env.server file:
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
This approach allows you to use Amazon SES API directly while still leveraging Wasp's authentication helpers. The key is to override the email sending functionality by implementing custom functions that use the AWS SDK instead of Wasp's built-in email sender. Note that this is a workaround and not an official solution. The Wasp team is considering adding more email provider options as mentioned in their GitHub issues, so this implementation might change in the future. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @saqib9810, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
saqib9810
saqib9810OP3d ago
// Use your custom signup action signup: { action: signup }
Can someone confirm if this is correct? I don't see this in the docs: https://wasp.sh/docs/auth/email#fields-in-the-email-dict
Overview | Wasp
Wasp supports e-mail authentication out of the box, along with email verification and "forgot your password?" flows. It provides you with the server-side implementation and email templates for all of these flows.
saqib9810
saqib9810OP3d ago
Tried it out and it didn't work. It would've been a nice solution.
miho
miho3d ago
It's possible to create a custom signup function if you want to fully customise the signup flow for the email auth provider: https://wasp.sh/docs/auth/advanced/custom-auth-actions
Custom sign-up actions | Wasp
If you need to deeply hook into the sign-up process, you can create your own sign-up action and customize the code to, for example, add extra validation, store more data, or otherwise call custom code at registration time.
saqib9810
saqib9810OP2d ago
Thanks @miho That would work for my use case.
MEE6
MEE62d ago
Wohooo @saqib9810, you just became a Waspeteer level 1!

Did you find this page helpful?