mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #3758 from omnivore-app/fix/mailjet-fallback
Add an email fallback to Mailjet
This commit is contained in:
commit
72c750dae1
3 changed files with 207 additions and 173 deletions
|
|
@ -86,6 +86,7 @@
|
|||
"luxon": "^3.2.1",
|
||||
"nanoid": "^3.1.25",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"node-mailjet": "^6.0.5",
|
||||
"nodemailer": "^6.7.3",
|
||||
"normalize-url": "^6.1.0",
|
||||
"oauth": "^0.10.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { env } from '../env'
|
||||
import { generateVerificationToken } from '../utils/auth'
|
||||
import { logger } from '../utils/logger'
|
||||
import { sendEmail } from '../utils/sendEmail'
|
||||
import mailjet from 'node-mailjet'
|
||||
|
||||
export const sendConfirmationEmail = async (user: {
|
||||
id: string
|
||||
|
|
@ -16,6 +18,10 @@ export const sendConfirmationEmail = async (user: {
|
|||
link,
|
||||
}
|
||||
|
||||
if (process.env.USE_MAILJET) {
|
||||
return sendWithMailJet(user.email, link)
|
||||
}
|
||||
|
||||
return sendEmail({
|
||||
from: env.sender.message,
|
||||
to: user.email,
|
||||
|
|
@ -24,6 +30,44 @@ export const sendConfirmationEmail = async (user: {
|
|||
})
|
||||
}
|
||||
|
||||
const sendWithMailJet = async (
|
||||
email: string,
|
||||
link: string
|
||||
): Promise<boolean> => {
|
||||
if (!process.env.MAILJET_API_KEY || !process.env.MAILGET_SECRET_KEY) {
|
||||
return false
|
||||
}
|
||||
|
||||
const client = mailjet.apiConnect(
|
||||
process.env.MAILJET_API_KEY,
|
||||
process.env.MAILGET_SECRET_KEY
|
||||
)
|
||||
|
||||
try {
|
||||
const request = await client.post('send', { version: 'v3.1' }).request({
|
||||
Messages: [
|
||||
{
|
||||
From: {
|
||||
Email: 'no-reply@omnivore.app',
|
||||
},
|
||||
To: [
|
||||
{
|
||||
Email: email,
|
||||
Name: 'Omnivore',
|
||||
},
|
||||
],
|
||||
Subject: 'Your Omnivore verification link',
|
||||
TextPart: `Your Omnivore verification link ${link}`,
|
||||
},
|
||||
],
|
||||
})
|
||||
} catch (err) {
|
||||
logger.error('error sending with mailjet', { err })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const sendVerificationEmail = async (user: {
|
||||
id: string
|
||||
name: string
|
||||
|
|
|
|||
|
|
@ -46,185 +46,174 @@ export function EmailSignup(): JSX.Element {
|
|||
)
|
||||
|
||||
return (
|
||||
<VStack alignment="center">
|
||||
<SpanBox css={{ color: 'red' }}>
|
||||
We have temporarily suspended signup via email while we work to resolve
|
||||
an issue with our email provider.
|
||||
</SpanBox>
|
||||
<SpanBox css={{ color: 'black' }}>
|
||||
<a href="https://discord.gg/h2z5rppzz9">
|
||||
Join our discord for more information
|
||||
</a>
|
||||
</SpanBox>
|
||||
</VStack>
|
||||
// <form action={`${fetchEndpoint}/auth/email-signup`} method="POST">
|
||||
// <VStack
|
||||
// alignment="center"
|
||||
// css={{
|
||||
// padding: '16px',
|
||||
// background: 'white',
|
||||
// minWidth: '340px',
|
||||
// width: '70vw',
|
||||
// maxWidth: '576px',
|
||||
// borderRadius: '8px',
|
||||
// border: '1px solid #3D3D3D',
|
||||
// boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
// }}
|
||||
// >
|
||||
// <StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
// Sign Up
|
||||
// </StyledText>
|
||||
// <VStack
|
||||
// css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}
|
||||
// >
|
||||
// <SpanBox css={{ width: '100%' }}>
|
||||
// <FormLabel className="required">Email</FormLabel>
|
||||
// <BorderedFormInput
|
||||
// autoFocus={true}
|
||||
// key="email"
|
||||
// type="email"
|
||||
// name="email"
|
||||
// defaultValue={email}
|
||||
// placeholder="Email"
|
||||
// css={{ backgroundColor: 'white', color: 'black' }}
|
||||
// onChange={(e) => {
|
||||
// e.preventDefault()
|
||||
// setEmail(e.target.value)
|
||||
// }}
|
||||
// required
|
||||
// />
|
||||
// </SpanBox>
|
||||
<form action={`${fetchEndpoint}/auth/email-signup`} method="POST">
|
||||
<VStack
|
||||
alignment="center"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
Sign Up
|
||||
</StyledText>
|
||||
<VStack
|
||||
css={{ width: '100%', minWidth: '320px', gap: '16px', pb: '16px' }}
|
||||
>
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
<FormLabel className="required">Email</FormLabel>
|
||||
<BorderedFormInput
|
||||
autoFocus={true}
|
||||
key="email"
|
||||
type="email"
|
||||
name="email"
|
||||
defaultValue={email}
|
||||
placeholder="Email"
|
||||
css={{ backgroundColor: 'white', color: 'black' }}
|
||||
onChange={(e) => {
|
||||
e.preventDefault()
|
||||
setEmail(e.target.value)
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</SpanBox>
|
||||
|
||||
// <SpanBox css={{ width: '100%' }}>
|
||||
// <FormLabel className="required">Password</FormLabel>
|
||||
// <BorderedFormInput
|
||||
// key="password"
|
||||
// type="password"
|
||||
// name="password"
|
||||
// defaultValue={password}
|
||||
// placeholder="Password"
|
||||
// css={{ bg: 'white', color: 'black' }}
|
||||
// onChange={(e) => setPassword(e.target.value)}
|
||||
// required
|
||||
// />
|
||||
// </SpanBox>
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
<FormLabel className="required">Password</FormLabel>
|
||||
<BorderedFormInput
|
||||
key="password"
|
||||
type="password"
|
||||
name="password"
|
||||
defaultValue={password}
|
||||
placeholder="Password"
|
||||
css={{ bg: 'white', color: 'black' }}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</SpanBox>
|
||||
|
||||
// <SpanBox css={{ width: '100%' }}>
|
||||
// <FormLabel className="required">Full Name</FormLabel>
|
||||
// <BorderedFormInput
|
||||
// key="fullname"
|
||||
// type="text"
|
||||
// name="name"
|
||||
// defaultValue={fullname}
|
||||
// placeholder="Full Name"
|
||||
// css={{ bg: 'white', color: 'black' }}
|
||||
// onChange={(e) => setFullname(e.target.value)}
|
||||
// required
|
||||
// />
|
||||
// </SpanBox>
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
<FormLabel className="required">Full Name</FormLabel>
|
||||
<BorderedFormInput
|
||||
key="fullname"
|
||||
type="text"
|
||||
name="name"
|
||||
defaultValue={fullname}
|
||||
placeholder="Full Name"
|
||||
css={{ bg: 'white', color: 'black' }}
|
||||
onChange={(e) => setFullname(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</SpanBox>
|
||||
|
||||
// <SpanBox css={{ width: '100%' }}>
|
||||
// <FormLabel className="required">Username</FormLabel>
|
||||
// <BorderedFormInput
|
||||
// key="username"
|
||||
// type="text"
|
||||
// name="username"
|
||||
// defaultValue={username}
|
||||
// placeholder="Username"
|
||||
// css={{ bg: 'white', color: 'black' }}
|
||||
// onChange={handleUsernameChange}
|
||||
// required
|
||||
// />
|
||||
// </SpanBox>
|
||||
// {username && username.length > 0 && usernameErrorMessage && (
|
||||
// <StyledText
|
||||
// style="caption"
|
||||
// css={{
|
||||
// m: 0,
|
||||
// pl: '$2',
|
||||
// color: '$error',
|
||||
// alignSelf: 'flex-start',
|
||||
// }}
|
||||
// >
|
||||
// {usernameErrorMessage}
|
||||
// </StyledText>
|
||||
// )}
|
||||
// {isUsernameValid && (
|
||||
// <StyledText
|
||||
// style="caption"
|
||||
// css={{
|
||||
// m: 0,
|
||||
// pl: '$2',
|
||||
// alignSelf: 'flex-start',
|
||||
// color: '$omnivoreGray',
|
||||
// }}
|
||||
// >
|
||||
// Username is available.
|
||||
// </StyledText>
|
||||
// )}
|
||||
// </VStack>
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
<FormLabel className="required">Username</FormLabel>
|
||||
<BorderedFormInput
|
||||
key="username"
|
||||
type="text"
|
||||
name="username"
|
||||
defaultValue={username}
|
||||
placeholder="Username"
|
||||
css={{ bg: 'white', color: 'black' }}
|
||||
onChange={handleUsernameChange}
|
||||
required
|
||||
/>
|
||||
</SpanBox>
|
||||
{username && username.length > 0 && usernameErrorMessage && (
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{
|
||||
m: 0,
|
||||
pl: '$2',
|
||||
color: '$error',
|
||||
alignSelf: 'flex-start',
|
||||
}}
|
||||
>
|
||||
{usernameErrorMessage}
|
||||
</StyledText>
|
||||
)}
|
||||
{isUsernameValid && (
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{
|
||||
m: 0,
|
||||
pl: '$2',
|
||||
alignSelf: 'flex-start',
|
||||
color: '$omnivoreGray',
|
||||
}}
|
||||
>
|
||||
Username is available.
|
||||
</StyledText>
|
||||
)}
|
||||
</VStack>
|
||||
|
||||
// {errorMessage && <StyledText style="error">{errorMessage}</StyledText>}
|
||||
{errorMessage && <StyledText style="error">{errorMessage}</StyledText>}
|
||||
|
||||
// <StyledText
|
||||
// style="caption"
|
||||
// css={{
|
||||
// p: '0px',
|
||||
// color: '$omnivoreLightGray',
|
||||
// }}
|
||||
// >
|
||||
// Omnivore will send you daily tips for your first week as a new user.
|
||||
// If you don't like them you can unsubscribe.
|
||||
// </StyledText>
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{
|
||||
p: '0px',
|
||||
color: '$omnivoreLightGray',
|
||||
}}
|
||||
>
|
||||
Omnivore will send you daily tips for your first week as a new user.
|
||||
If you don't like them you can unsubscribe.
|
||||
</StyledText>
|
||||
|
||||
// <HStack
|
||||
// alignment="center"
|
||||
// distribution="end"
|
||||
// css={{
|
||||
// gap: '10px',
|
||||
// width: '100%',
|
||||
// height: '80px',
|
||||
// }}
|
||||
// >
|
||||
// <Button
|
||||
// style={'ctaOutlineYellow'}
|
||||
// css={{ color: '$omnivoreGray', borderColor: 'rgba(0, 0, 0, 0.06)' }}
|
||||
// type="button"
|
||||
// onClick={async () => {
|
||||
// window.localStorage.removeItem('authVerified')
|
||||
// window.localStorage.removeItem('authToken')
|
||||
// try {
|
||||
// await logoutMutation()
|
||||
// } catch (e) {
|
||||
// console.log('error logging out', e)
|
||||
// }
|
||||
// window.location.href = '/'
|
||||
// }}
|
||||
// >
|
||||
// Cancel
|
||||
// </Button>
|
||||
// <Button type="submit" style={'ctaDarkYellow'}>
|
||||
// Sign Up
|
||||
// </Button>
|
||||
// </HStack>
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="end"
|
||||
css={{
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
height: '80px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={'ctaOutlineYellow'}
|
||||
css={{ color: '$omnivoreGray', borderColor: 'rgba(0, 0, 0, 0.06)' }}
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
window.localStorage.removeItem('authVerified')
|
||||
window.localStorage.removeItem('authToken')
|
||||
try {
|
||||
await logoutMutation()
|
||||
} catch (e) {
|
||||
console.log('error logging out', e)
|
||||
}
|
||||
window.location.href = '/'
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" style={'ctaDarkYellow'}>
|
||||
Sign Up
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
// <StyledText
|
||||
// style="action"
|
||||
// css={{
|
||||
// pt: '16px',
|
||||
// color: '$omnivoreLightGray',
|
||||
// textAlign: 'center',
|
||||
// }}
|
||||
// >
|
||||
// Already have an account?{' '}
|
||||
// <Link href="/auth/email-login" passHref legacyBehavior>
|
||||
// <StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
// Login instead
|
||||
// </StyledTextSpan>
|
||||
// </Link>
|
||||
// </StyledText>
|
||||
// <TermAndConditionsFooter />
|
||||
// </VStack>
|
||||
// </form>
|
||||
<StyledText
|
||||
style="action"
|
||||
css={{
|
||||
pt: '16px',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Already have an account?{' '}
|
||||
<Link href="/auth/email-login" passHref legacyBehavior>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Login instead
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
<TermAndConditionsFooter />
|
||||
</VStack>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue