This commit is contained in:
vadim committed 2026-10-23 19:26:26 +03:00
1 parent de1b3754df
commit f085517732
3 files changed
+45 -18

No files matched your search

+11 -15
View File
@@ -12,10 +12,7 @@ export const auth = betterAuth({
emailOTP({ emailOTP({
async sendVerificationOTP({ email, otp, type }) { async sendVerificationOTP({ email, otp, type }) {
if (type === 'sign-in') { if (type === 'sign-in') {
// Send the OTP for sign in EmailSender.sendInBackground({
console.log({ email, otp, type });
void EmailSender.getTransporter().sendMail({
to: email, to: email,
subject: `Your OTP code is: ${otp}`, subject: `Your OTP code is: ${otp}`,
text: `Your OTP code is: ${otp}`, text: `Your OTP code is: ${otp}`,
@@ -23,8 +20,13 @@ export const auth = betterAuth({
} else if (type === 'email-verification') { } else if (type === 'email-verification') {
// Send the OTP for email verification // Send the OTP for email verification
console.log({ email, otp, type }); console.log({ email, otp, type });
} else if (type === 'forget-password') {
EmailSender.sendInBackground({
to: email,
subject: `Your password reset code is: ${otp}`,
text: `Your password reset code is: ${otp}`,
});
} else { } else {
// Send the OTP for password reset
console.log({ email, otp, type }); console.log({ email, otp, type });
} }
}, },
@@ -47,26 +49,21 @@ export const auth = betterAuth({
autoSignIn: false, autoSignIn: false,
requireEmailVerification: true, requireEmailVerification: true,
onExistingUserSignUp: async ({ user }, request) => { onExistingUserSignUp: async ({ user }, request) => {
console.log('onExistingUserSignUp', { user }); EmailSender.sendInBackground({
void EmailSender.getTransporter().sendMail({
to: user.email, to: user.email,
subject: 'Sign-up attempt with your email', subject: 'Sign-up attempt with your email',
text: 'Someone tried to create an account using your email address. If this was you, try signing in instead. If not, you can safely ignore this email.', text: 'Someone tried to create an account using your email address. If this was you, try signing in instead. If not, you can safely ignore this email.',
}); });
}, },
sendResetPassword: async ({ user, url, token }, request) => { sendResetPassword: async ({ user, url, token }, request) => {
console.log('sendResetPassword', { user, url, token }); EmailSender.sendInBackground({
void EmailSender.getTransporter().sendMail({
to: user.email, to: user.email,
subject: 'Reset your password', subject: 'Reset your password',
text: `Click the link to reset your password: ${url}`, text: `Click the link to reset your password: ${url}`,
}); });
}, },
onPasswordReset: async ({ user }, request) => { onPasswordReset: async ({ user }, request) => {
// your logic here EmailSender.sendInBackground({
console.log('onPasswordReset', { user });
void EmailSender.getTransporter().sendMail({
to: user.email, to: user.email,
subject: 'Reset your password result', subject: 'Reset your password result',
text: `Password for user ${user.email} has been reset.`, text: `Password for user ${user.email} has been reset.`,
@@ -77,8 +74,7 @@ export const auth = betterAuth({
sendOnSignIn: true, sendOnSignIn: true,
autoSignInAfterVerification: true, autoSignInAfterVerification: true,
sendVerificationEmail: async ({ user, url }) => { sendVerificationEmail: async ({ user, url }) => {
console.log('emailVerification', { user, url }); EmailSender.sendInBackground({
void EmailSender.getTransporter().sendMail({
to: user.email, to: user.email,
subject: 'Verify your email address', subject: 'Verify your email address',
text: `Click the link to verify your email: ${url}`, text: `Click the link to verify your email: ${url}`,
+33 -2
View File
@@ -1,13 +1,44 @@
import { Logger } from '@nestjs/common';
import nodemailer, { SendMailOptions, Transporter } from 'nodemailer'; import nodemailer, { SendMailOptions, Transporter } from 'nodemailer';
export default class EmailSender { export default class EmailSender {
static getTransporter() { private static readonly logger = new Logger(EmailSender.name);
return nodemailer.createTransport({ private static transporter?: Transporter;
private static isLocal() {
return process.env.NODE_ENV === 'local';
}
private static getTransporter() {
this.transporter ??= nodemailer.createTransport({
service: 'gmail', service: 'gmail',
auth: { auth: {
user: process.env.GOOGLE_APP_USER, user: process.env.GOOGLE_APP_USER,
pass: process.env.GOOGLE_APP_PASSWORD, pass: process.env.GOOGLE_APP_PASSWORD,
}, },
}); });
return this.transporter;
}
/** Sends an email; throws on failure. In local env only logs the message. */
static async send(options: SendMailOptions): Promise<void> {
if (this.isLocal()) {
this.logger.log(
`[local] email not sent\n to: ${String(options.to)}\n subject: ${options.subject}\n text: ${String(options.text)}`,
);
return;
}
await this.getTransporter().sendMail(options);
}
/** Fire-and-forget variant: never throws, errors are logged. */
static sendInBackground(options: SendMailOptions): void {
this.send(options).catch((error: unknown) => {
this.logger.error(
`Failed to send email "${options.subject}" to ${String(options.to)}`,
error instanceof Error ? error.stack : String(error),
);
});
} }
} }
@@ -16,7 +16,7 @@ export class EmailChannelSender implements NotificationChannelSender {
select: { email: true }, select: { email: true },
}); });
await EmailSender.getTransporter().sendMail({ await EmailSender.send({
to: user.email, to: user.email,
subject: params.subject ?? 'Уведомление', subject: params.subject ?? 'Уведомление',
text: params.content, text: params.content,