This repository has been archived by the owner on Jan 16, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce email-templates; redis; bullmq (#31)
- Loading branch information
1 parent
693d9ec
commit 2679783
Showing
38 changed files
with
4,464 additions
and
1,213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,5 @@ _shared | |
node_modules | ||
tmp | ||
dist | ||
.cache | ||
.cache | ||
pnpm-lock.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {render} from '@white-label/email-templates'; | ||
import * as Effect from 'effect/Effect'; | ||
|
||
export function buildTemplate(template: React.ReactElement) { | ||
return Effect.try({ | ||
// eslint-disable-next-line | ||
try: () => render(template), | ||
catch: () => { | ||
return Effect.fail('Failed to render email template'); | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import 'dotenv/config'; | ||
|
||
import * as Schema from '@effect/schema/Schema'; | ||
|
||
const envValidationSchema = Schema.struct({ | ||
SMTP_HOST: Schema.string.pipe(Schema.minLength(2)), | ||
SMTP_USER: Schema.string.pipe(Schema.minLength(2)), | ||
SMTP_PASSWORD: Schema.string.pipe(Schema.minLength(2)), | ||
SMTP_PORT: Schema.NumberFromString, | ||
SMTP_SECURE: Schema.string.pipe(Schema.nonEmpty()), | ||
|
||
EMAIL_FROM: Schema.string.pipe(Schema.minLength(5), Schema.endsWith('.com')), | ||
DASHBOARD_URL: Schema.string.pipe( | ||
Schema.minLength(5), | ||
Schema.startsWith('http') | ||
), | ||
}); | ||
|
||
// Throw on-load if missing | ||
export const config = Schema.parseSync(envValidationSchema)(process.env); |
52 changes: 52 additions & 0 deletions
52
apps/dashboard/app/mailer/emails/send-invitation-email.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import {InvitationEmailTemplate} from '@white-label/email-templates'; | ||
import * as Effect from 'effect/Effect'; | ||
import {pipe} from 'effect/Function'; | ||
|
||
import {addEmailJob} from '@/queues/email-queue'; | ||
|
||
import {buildTemplate} from '../build-template.server'; | ||
import {config} from '../config.server'; | ||
|
||
export function sendInvitationEmail({ | ||
email, | ||
orgName, | ||
invitationTokenId, | ||
}: { | ||
email: string; | ||
orgName: string; | ||
invitationTokenId: string; | ||
}) { | ||
return Effect.gen(function* (_) { | ||
yield* _(Effect.log('Mailer(invitation-email): Preparing email')); | ||
// eslint-disable-next-line | ||
const html = yield* _( | ||
buildTemplate( | ||
<InvitationEmailTemplate | ||
orgName={orgName} | ||
dashboardUrl={config.DASHBOARD_URL} | ||
invitationDeclineUrl={`${config.DASHBOARD_URL}/invitation/decline?invitationId=${invitationTokenId}`} | ||
/> | ||
) | ||
); | ||
|
||
const payload = { | ||
to: email, | ||
subject: `You've been invited to join ${orgName}`, | ||
content: html, | ||
}; | ||
|
||
yield* _(addEmailJob('invitation-email', payload)); | ||
yield* _(Effect.log('Mailer(invitation-email): Sending email')); | ||
}).pipe( | ||
Effect.catchAll((error) => | ||
pipe( | ||
Effect.log( | ||
`Mailer(invitation-email): Failed to send email to ${email}` | ||
), | ||
Effect.flatMap(() => Effect.log(error)), | ||
// suppress error | ||
Effect.flatMap(() => Effect.unit) | ||
) | ||
) | ||
); | ||
} |
49 changes: 49 additions & 0 deletions
49
apps/dashboard/app/mailer/emails/send-password-reset-email.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import {PasswordResetEmailTemplate} from '@white-label/email-templates'; | ||
import * as Effect from 'effect/Effect'; | ||
import {pipe} from 'effect/Function'; | ||
|
||
import {addEmailJob} from '@/queues/email-queue'; | ||
|
||
import {buildTemplate} from '../build-template.server'; | ||
import {config} from '../config.server'; | ||
|
||
export function sendPasswordResetEmail({ | ||
email, | ||
passwordResetTokenId, | ||
}: { | ||
email: string; | ||
passwordResetTokenId: string; | ||
}) { | ||
return Effect.gen(function* (_) { | ||
yield* _(Effect.log('Mailer(password-reset-email): Preparing email')); | ||
// eslint-disable-next-line | ||
const html = yield* _( | ||
buildTemplate( | ||
<PasswordResetEmailTemplate | ||
dashboardUrl={config.DASHBOARD_URL} | ||
passwordResetUrl={`${config.DASHBOARD_URL}/password/reset-password?token=${passwordResetTokenId}`} | ||
/> | ||
) | ||
); | ||
|
||
const payload = { | ||
to: email, | ||
subject: `Reset your password`, | ||
content: html, | ||
}; | ||
|
||
yield* _(addEmailJob('password-reset-email', payload)); | ||
yield* _(Effect.log('Mailer(password-reset-email): Sending email')); | ||
}).pipe( | ||
Effect.catchAll((error) => | ||
pipe( | ||
Effect.log( | ||
`Mailer(password-reset-email): Failed to send email to ${email}` | ||
), | ||
Effect.flatMap(() => Effect.log(error)), | ||
// suppress error | ||
Effect.flatMap(() => Effect.unit) | ||
) | ||
) | ||
); | ||
} |
51 changes: 51 additions & 0 deletions
51
apps/dashboard/app/mailer/emails/send-verification-email.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import {VerificationEmailTemplate} from '@white-label/email-templates'; | ||
import * as Effect from 'effect/Effect'; | ||
import {pipe} from 'effect/Function'; | ||
|
||
import {addEmailJob} from '@/queues/email-queue'; | ||
|
||
import {buildTemplate} from '../build-template.server'; | ||
import {config} from '../config.server'; | ||
|
||
export function sendVerificationEmail({ | ||
email, | ||
verifyEmailTokenId, | ||
}: { | ||
email: string; | ||
verifyEmailTokenId: string; | ||
}) { | ||
return Effect.gen(function* (_) { | ||
yield* _( | ||
Effect.log(`Mailer(verification-email): Sending email to ${email}`) | ||
); | ||
// eslint-disable-next-line | ||
const html = yield* _( | ||
buildTemplate( | ||
<VerificationEmailTemplate | ||
dashboardUrl={config.DASHBOARD_URL} | ||
verificationUrl={`${config.DASHBOARD_URL}/email/verify-email?token=${verifyEmailTokenId}`} | ||
/> | ||
) | ||
); | ||
|
||
const payload = { | ||
to: email, | ||
subject: `Verify your email`, | ||
content: html, | ||
}; | ||
|
||
yield* _(addEmailJob('password-reset-email', payload)); | ||
yield* _(Effect.log(`Mailer(verification-email): Sent email to ${email}`)); | ||
}).pipe( | ||
Effect.catchAll((error) => | ||
pipe( | ||
Effect.log( | ||
`Mailer(verification-email): Failed to send email to ${email}` | ||
), | ||
Effect.flatMap(() => Effect.log(error)), | ||
// suppress error | ||
Effect.flatMap(() => Effect.unit) | ||
) | ||
) | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// import * as Effect from 'effect/Effect'; | ||
|
||
import {config} from './config.server'; | ||
import {transporter} from './transporter.server'; | ||
|
||
interface SendEmailProps { | ||
to: string; | ||
subject: string; | ||
content: string; | ||
} | ||
|
||
// todo: pass through context | ||
export function sendEmail(props: SendEmailProps) { | ||
return transporter.sendMail({ | ||
from: config.EMAIL_FROM, | ||
to: props.to, | ||
subject: props.subject, | ||
html: props.content, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import {createTransport} from 'nodemailer'; | ||
|
||
import {config} from './config.server'; | ||
|
||
function makeTransporter() { | ||
return createTransport({ | ||
host: config.SMTP_HOST, | ||
port: config.SMTP_PORT, | ||
secure: config.SMTP_SECURE === 'true', | ||
auth: { | ||
user: config.SMTP_USER, | ||
pass: config.SMTP_PASSWORD, | ||
}, | ||
pool: true, | ||
}); | ||
} | ||
|
||
let transporter: ReturnType<typeof makeTransporter>; | ||
|
||
declare global { | ||
// eslint-disable-next-line no-var | ||
var __transporter: ReturnType<typeof makeTransporter> | undefined; | ||
} | ||
|
||
if (process.env.NODE_ENV === 'production') { | ||
transporter = makeTransporter(); | ||
} else { | ||
if (!global.__transporter) { | ||
global.__transporter = makeTransporter(); | ||
} | ||
transporter = global.__transporter; | ||
} | ||
|
||
export {transporter}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.