diff --git a/apps/web/public/images/rallly-logo-mark.png b/apps/web/public/images/rallly-logo-mark.png new file mode 100644 index 00000000000..13ef1b75104 Binary files /dev/null and b/apps/web/public/images/rallly-logo-mark.png differ diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index f621462eb2a..c26e2327946 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -49,6 +49,12 @@ export const env = createEnv({ * Example: "user@example.com, *@example.com, *@*.example.com" */ ALLOWED_EMAILS: z.string().optional(), + /** + * Email addresses for support and no-reply emails. + */ + SUPPORT_EMAIL: z.string().email(), + NOREPLY_EMAIL: z.string().email().optional(), + NOREPLY_EMAIL_NAME: z.string().default("Rallly"), }, /* * Environment variables available on the client (and server). @@ -90,6 +96,9 @@ export const env = createEnv({ NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY, NEXT_PUBLIC_POSTHOG_API_HOST: process.env.NEXT_PUBLIC_POSTHOG_API_HOST, NEXT_PUBLIC_SELF_HOSTED: process.env.NEXT_PUBLIC_SELF_HOSTED, + SUPPORT_EMAIL: process.env.SUPPORT_EMAIL, + NOREPLY_EMAIL: process.env.NOREPLY_EMAIL, + NOREPLY_EMAIL_NAME: process.env.NOREPLY_EMAIL_NAME, }, skipValidation: !!process.env.SKIP_ENV_VALIDATION, }); diff --git a/apps/web/src/utils/emails.ts b/apps/web/src/utils/emails.ts index 130c574712e..11e6376f371 100644 --- a/apps/web/src/utils/emails.ts +++ b/apps/web/src/utils/emails.ts @@ -1,25 +1,26 @@ import { EmailClient, SupportedEmailProviders } from "@rallly/emails"; +import { env } from "@/env"; import { absoluteUrl } from "@/utils/absolute-url"; - -const env = process.env["NODE" + "_ENV"]; +import { isSelfHosted } from "@/utils/constants"; export const emailClient = new EmailClient({ - openPreviews: env === "development", + openPreviews: env.NODE_ENV === "development", provider: { name: (process.env.EMAIL_PROVIDER as SupportedEmailProviders) ?? "smtp", }, mail: { from: { - name: (process.env.NOREPLY_EMAIL_NAME as string) || "Rallly", - address: - (process.env.NOREPLY_EMAIL as string) || - (process.env.SUPPORT_EMAIL as string), + name: env.NOREPLY_EMAIL_NAME, + address: env.NOREPLY_EMAIL || env.SUPPORT_EMAIL, }, }, context: { - logoUrl: absoluteUrl("/logo.png"), + logoUrl: isSelfHosted + ? absoluteUrl("/images/rallly-logo-mark.png") + : "https://rallly-public.s3.amazonaws.com/images/rallly-logo-mark.png", baseUrl: absoluteUrl(""), domain: absoluteUrl("").replace(/(^\w+:|^)\/\//, ""), + supportEmail: env.SUPPORT_EMAIL, }, }); diff --git a/packages/emails/src/templates/_components/email-context.tsx b/packages/emails/src/templates/_components/email-context.tsx index 34159783a05..966d5c46834 100644 --- a/packages/emails/src/templates/_components/email-context.tsx +++ b/packages/emails/src/templates/_components/email-context.tsx @@ -2,10 +2,12 @@ export type EmailContext = { logoUrl: string; baseUrl: string; domain: string; + supportEmail: string; }; export const defaultEmailContext = { - logoUrl: "https://rallly.co/logo.png", + logoUrl: "https://rallly-public.s3.amazonaws.com/images/rallly-logo-mark.png", baseUrl: "https://rallly.co", domain: "rallly.co", + supportEmail: "support@rallly.co", }; diff --git a/packages/emails/src/templates/_components/email-layout.tsx b/packages/emails/src/templates/_components/email-layout.tsx index 4f7a652922f..e9cf91c211f 100644 --- a/packages/emails/src/templates/_components/email-layout.tsx +++ b/packages/emails/src/templates/_components/email-layout.tsx @@ -4,97 +4,56 @@ import { Head, Html, Img, - Link, Preview, + Section, } from "@react-email/components"; import { EmailContext } from "./email-context"; -import { fontFamily, Section, Text } from "./styled-components"; +import { darkTextColor, fontFamily, Link, Text } from "./styled-components"; export interface EmailLayoutProps { preview: string; - recipientName?: string; - footNote?: React.ReactNode; ctx: EmailContext; } const containerStyles = { - maxWidth: "600px", + maxWidth: "480px", margin: "0 auto", background: "white", fontFamily, - padding: 16, - border: "1px solid #E2E8F0", - borderRadius: 5, -}; - -const sectionStyles = { - marginTop: "16px", - marginBottom: "16px", -}; - -const linkStyles = { - color: "#64748B", - marginRight: "8px", + padding: "32px 8px", + color: darkTextColor, }; export const EmailLayout = ({ preview, - recipientName, children, - footNote, ctx, }: React.PropsWithChildren) => { - const { logoUrl, baseUrl } = ctx; + const { logoUrl } = ctx; return ( {preview} - + - Rallly -
- {recipientName ? Hi {recipientName}, : null} - {children} - {footNote ? ( - - {footNote} - - ) : null} -
-
- - Home - -  •  - - Twitter - -  •  - - Github - -  •  - - Contact - + Rallly Logo + {children} +
+ + Powered by{" "} + + rallly.co + +
diff --git a/packages/emails/src/templates/_components/notification-email.tsx b/packages/emails/src/templates/_components/notification-email.tsx index ecb54450a62..60d8a0d33c4 100644 --- a/packages/emails/src/templates/_components/notification-email.tsx +++ b/packages/emails/src/templates/_components/notification-email.tsx @@ -1,3 +1,5 @@ +import { Section } from "@react-email/section"; + import { EmailContext } from "./email-context"; import { EmailLayout } from "./email-layout"; import { Button, Link, Text } from "./styled-components"; @@ -15,7 +17,6 @@ export interface NotificationEmailProps extends NotificationBaseProps { } export const NotificationEmail = ({ - name, pollUrl, disableNotificationsUrl, preview, @@ -24,23 +25,17 @@ export const NotificationEmail = ({ }: React.PropsWithChildren) => { const { domain } = ctx; return ( - - If you would like to stop receiving updates you can{" "} - - turn notifications off - - . - - } - preview={preview} - > + {children} - +
+
+ + If you would like to stop receiving updates you can{" "} + + turn notifications off + + .
); diff --git a/packages/emails/src/templates/_components/styled-components.tsx b/packages/emails/src/templates/_components/styled-components.tsx index d4c7cc8c314..eeaf2ccc435 100644 --- a/packages/emails/src/templates/_components/styled-components.tsx +++ b/packages/emails/src/templates/_components/styled-components.tsx @@ -11,6 +11,8 @@ import { import { EmailContext } from "./email-context"; +export const lightTextColor = "#4B5563"; +export const darkTextColor = "#1F2937"; export const borderColor = "#E2E8F0"; export const Text = ( props: TextProps & { light?: boolean; small?: boolean }, @@ -23,7 +25,7 @@ export const Text = ( margin: "16px 0", fontFamily, fontSize: small ? "14px" : "16px", - color: light ? "#64748B" : "#334155", + color: light ? lightTextColor : darkTextColor, lineHeight: "1.5", ...props.style, }} @@ -46,6 +48,11 @@ export const Button = (props: React.ComponentProps) => { borderRadius: "4px", padding: "12px 14px", fontFamily, + boxSizing: "border-box", + display: "block", + width: "100%", + maxWidth: "100%", + textAlign: "center", fontSize: "16px", color: "white", }} @@ -62,23 +69,24 @@ export const Link = (props: LinkProps) => { ); }; +const fontSize = { + h1: "20px", + h2: "18px", + h3: "16px", + h4: "16px", + h5: "14px", + h6: "12px", +}; + export const Heading = ( props: React.ComponentProps, ) => { - const { as = "h3" } = props; - const fontSize = { - h1: "32px", - h2: "24px", - h3: "20px", - h4: "16px", - h5: "14px", - h6: "12px", - }; + const { as = "h1" } = props; + return ( { return ( - + + Final date booked! {title} has been booked for: @@ -73,9 +78,9 @@ export const FinalizeHostEmail = ({ We've notified participants and sent them calendar invites. - +
- +
); }; diff --git a/packages/emails/src/templates/finalized-participant.tsx b/packages/emails/src/templates/finalized-participant.tsx index 6dfffa6d916..73fc37b844d 100644 --- a/packages/emails/src/templates/finalized-participant.tsx +++ b/packages/emails/src/templates/finalized-participant.tsx @@ -2,7 +2,12 @@ import { Column, Row, Section } from "@react-email/components"; import { defaultEmailContext, EmailContext } from "./_components/email-context"; import { EmailLayout } from "./_components/email-layout"; -import { borderColor, Button, Text } from "./_components/styled-components"; +import { + borderColor, + Button, + Heading, + Text, +} from "./_components/styled-components"; export interface FinalizeParticipantEmailProps { date: string; @@ -19,7 +24,6 @@ export interface FinalizeParticipantEmailProps { } export const FinalizeParticipantEmail = ({ - name = "Guest", title = "Untitled Poll", hostName = "Host", pollUrl = "https://rallly.co", @@ -30,12 +34,13 @@ export const FinalizeParticipantEmail = ({ ctx = defaultEmailContext, }: FinalizeParticipantEmailProps) => { return ( - + + Final date booked! {hostName} has booked {title} for the following date: -
+
Please find attached a calendar invite for this event. - +
- +
); }; diff --git a/packages/emails/src/templates/login.tsx b/packages/emails/src/templates/login.tsx index 972f0e389d3..32a993735f0 100644 --- a/packages/emails/src/templates/login.tsx +++ b/packages/emails/src/templates/login.tsx @@ -1,3 +1,5 @@ +import { Section } from "@react-email/components"; + import { defaultEmailContext, EmailContext } from "./_components/email-context"; import { EmailLayout } from "./_components/email-layout"; import { @@ -17,43 +19,40 @@ interface LoginEmailProps { } export const LoginEmail = ({ - name = "Guest", code = "123456", magicLink = "https://rallly.co", ctx = defaultEmailContext, }: LoginEmailProps) => { return ( - - You're receiving this email because a request was made to login - to . If this wasn't you, let us know by - replying to this email. - - } - recipientName={name} - preview="Use this link to log in on this device." - > - - To log in to your account, please choose one of the following options: - - - Option 1: Magic Link - Click this magic link to log in on this device. + + Login + Enter this one-time 6-digit verification code: + + + {code} + + + This code is valid for 15 minutes + + +
- This link will expire in 15 minutes. - - - Option 2: Verification Code - Enter this one-time 6-digit verification code. - - {code} - - This code will expire in 15 minutes. - +
+ + You're receiving this email because a request was made to login to{" "} + . If this wasn't you contact{" "} + {ctx.supportEmail}. +
); }; diff --git a/packages/emails/src/templates/new-comment.tsx b/packages/emails/src/templates/new-comment.tsx index a36cc7438c6..59aa15e6e9a 100644 --- a/packages/emails/src/templates/new-comment.tsx +++ b/packages/emails/src/templates/new-comment.tsx @@ -2,7 +2,7 @@ import { defaultEmailContext } from "./_components/email-context"; import NotificationEmail, { NotificationBaseProps, } from "./_components/notification-email"; -import { Text } from "./_components/styled-components"; +import { Heading, Text } from "./_components/styled-components"; export interface NewCommentEmailProps extends NotificationBaseProps { authorName: string; @@ -25,6 +25,7 @@ export const NewCommentEmail = ({ disableNotificationsUrl={disableNotificationsUrl} preview="Go to your poll to see what they said." > + New Comment {authorName} has commented on {title}. diff --git a/packages/emails/src/templates/new-participant-confirmation.tsx b/packages/emails/src/templates/new-participant-confirmation.tsx index 0fc2f49e439..662117a2f36 100644 --- a/packages/emails/src/templates/new-participant-confirmation.tsx +++ b/packages/emails/src/templates/new-participant-confirmation.tsx @@ -1,6 +1,12 @@ import { defaultEmailContext, EmailContext } from "./_components/email-context"; import { EmailLayout } from "./_components/email-layout"; -import { Button, Domain, Section, Text } from "./_components/styled-components"; +import { + Button, + Domain, + Heading, + Section, + Text, +} from "./_components/styled-components"; interface NewParticipantConfirmationEmailProps { name: string; @@ -10,24 +16,13 @@ interface NewParticipantConfirmationEmailProps { } export const NewParticipantConfirmationEmail = ({ title = "Untitled Poll", - name = "John", editSubmissionUrl = "https://rallly.co", ctx = defaultEmailContext, }: NewParticipantConfirmationEmailProps) => { const { domain } = ctx; return ( - - You are receiving this email because a response was submitted on{" "} - . If this wasn't you, please ignore this - email. - - } - recipientName={name} - preview="To edit your response use the link below" - > + + Poll Response Confirmation Your response to {title} has been submitted. @@ -35,11 +30,15 @@ export const NewParticipantConfirmationEmail = ({ While the poll is still open you can change your response using the link below.
-
+
+ + You are receiving this email because a response was submitted on{" "} + . If this wasn't you, please ignore this email. + ); }; diff --git a/packages/emails/src/templates/new-participant.tsx b/packages/emails/src/templates/new-participant.tsx index 09b4cee2174..e053a0f0c7a 100644 --- a/packages/emails/src/templates/new-participant.tsx +++ b/packages/emails/src/templates/new-participant.tsx @@ -2,7 +2,7 @@ import { defaultEmailContext } from "./_components/email-context"; import NotificationEmail, { NotificationBaseProps, } from "./_components/notification-email"; -import { Text } from "./_components/styled-components"; +import { Heading, Text } from "./_components/styled-components"; export interface NewParticipantEmailProps extends NotificationBaseProps { participantName: string; @@ -25,10 +25,12 @@ export const NewParticipantEmail = ({ disableNotificationsUrl={disableNotificationsUrl} preview="Go to your poll to see the new response." > + New Response {participantName} has responded to{" "} {title}. + Go to your poll to see the new response. ); }; diff --git a/packages/emails/src/templates/new-poll.tsx b/packages/emails/src/templates/new-poll.tsx index 3133816a911..e1222c07fd6 100644 --- a/packages/emails/src/templates/new-poll.tsx +++ b/packages/emails/src/templates/new-poll.tsx @@ -1,6 +1,12 @@ import { defaultEmailContext, EmailContext } from "./_components/email-context"; import { EmailLayout } from "./_components/email-layout"; -import { Button, Card, Link, Text } from "./_components/styled-components"; +import { + Button, + Card, + Heading, + Link, + Text, +} from "./_components/styled-components"; export interface NewPollEmailProps { title: string; @@ -10,82 +16,28 @@ export interface NewPollEmailProps { ctx: EmailContext; } -const ShareLink = ({ - title, - participantLink, - name, - children, -}: React.PropsWithChildren<{ - name: string; - title: string; - participantLink: string; -}>) => { - return ( - - {children} - - ); -}; - export const NewPollEmail = ({ title = "Untitled Poll", - name = "John", adminLink = "https://rallly.co/admin/abcdefg123", participantLink = "https://rallly.co/invite/wxyz9876", ctx = defaultEmailContext, }: NewPollEmailProps) => { - const { baseUrl, domain } = ctx; return ( - You are receiving this email because a new poll was created with this - email address on {domain}. If this - wasn't you, please ignore this email. - - } - recipientName={name} preview="Share your participant link to start collecting responses." > + New Poll Created - Your poll has been successfully created! Here are the details: + Your meeting poll titled {`"${title}"`} is ready! Share + it using the link below: - - - Title: {title} -
- Invite Link:{" "} + + {participantLink} - - - Share via email - - - - To invite participants to your poll, simply share the{" "} - Invite Link above with them. They'll be able to - vote on their preferred meeting times and dates. - - - If you need to make any changes to your poll, or if you want to see the - results so far, just click on the button below: - - - - +
); }; diff --git a/packages/emails/src/templates/register.tsx b/packages/emails/src/templates/register.tsx index 9a4c5bfb4d0..232d840a317 100644 --- a/packages/emails/src/templates/register.tsx +++ b/packages/emails/src/templates/register.tsx @@ -1,6 +1,9 @@ +import { Section } from "@react-email/section"; + import { defaultEmailContext, EmailContext } from "./_components/email-context"; import { EmailLayout } from "./_components/email-layout"; import { + Card, Domain, Heading, Text, @@ -17,24 +20,34 @@ export const RegisterEmail = ({ ctx = defaultEmailContext, }: RegisterEmailProps) => { return ( - - You're receiving this email because a request was made to - register an account on . If this wasn't you, - please ignore this email. - - } - preview={`Your 6-digit code is: ${code}`} - > + + Verify your email address Please use the following 6-digit verification code to verify your email: - - {code} - - This code is valid for 15 minutes + + + {code} + + + This code is valid for 15 minutes + + +
+ + You're receiving this email because a request was made to + register an account on . If this wasn't you, + please ignore this email. + +
); };