Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/ci-backend-email.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: CI checks for backend/email workspace

on:
pull_request:
branches: ['main']
paths: ['backend/email/**/*', '!**/*.{txt,md,png}']
push:
branches: ['main']
paths: ['backend/email/**/*', '!**/*.{txt,md,png}']

defaults:
run:
working-directory: backend/email

jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Run ESLint
run: yarn eslint .
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Run prettier
run: yarn prettier . --check --ignore-unknown
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Use tsc to build
run: yarn tsc -b
- name: Use tsc to typecheck
run: yarn tsc --noEmit --strict
1 change: 1 addition & 0 deletions backend/email/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib/**/*
24 changes: 9 additions & 15 deletions backend/email/emails/functions/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ export const sendNewMatchEmail = async (
privateUser: PrivateUser,
matchedWithUser: User
) => {
const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
privateUser,
'new_match'
)
const { sendToEmail /*, unsubscribeUrl*/ } =
getNotificationDestinationsForUser(privateUser, 'new_match')
if (!privateUser.email || !sendToEmail) return
Comment on lines +16 to 18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented unsubscribeUrl artifacts

Commented destructuring and props increase noise and can confuse future refactors. Remove them until the feature is implemented.

-  const { sendToEmail /*, unsubscribeUrl*/ } =
-    getNotificationDestinationsForUser(privateUser, 'new_match')
+  const { sendToEmail } =
+    getNotificationDestinationsForUser(privateUser, 'new_match')
@@
       <NewMatchEmail
         onUser={lover.user}
         matchedWithUser={matchedWithUser}
         matchedLover={lover}
-        /*unsubscribeUrl={unsubscribeUrl}*/
       />

Also applies to: 31-32

🤖 Prompt for AI Agents
In backend/email/emails/functions/helpers.tsx around lines 16-18 (and similarly
at lines 31-32), remove the commented-out unsubscribeUrl from the destructuring
and any commented props or variables related to unsubscribeUrl so the code only
destructures active fields (e.g., const { sendToEmail } =
getNotificationDestinationsForUser(...)), and delete the corresponding commented
usages to eliminate noise; if the unsubscribe feature is needed later,
reintroduce it as live code with tests.

const lover = await getLover(privateUser.id)
if (!lover) return
Expand All @@ -30,7 +28,7 @@ export const sendNewMatchEmail = async (
onUser={lover.user}
matchedWithUser={matchedWithUser}
matchedLover={lover}
unsubscribeUrl={unsubscribeUrl}
/*unsubscribeUrl={unsubscribeUrl}*/
/>
),
})
Expand All @@ -42,10 +40,8 @@ export const sendNewMessageEmail = async (
toUser: User,
channelId: number
) => {
const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
privateUser,
'new_message'
)
const { sendToEmail /*, unsubscribeUrl*/ } =
getNotificationDestinationsForUser(privateUser, 'new_message')
if (!privateUser.email || !sendToEmail) return
Comment on lines +43 to 45
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented unsubscribeUrl in sendNewMessageEmail

Same rationale as above.

-  const { sendToEmail /*, unsubscribeUrl*/ } =
-    getNotificationDestinationsForUser(privateUser, 'new_message')
+  const { sendToEmail } =
+    getNotificationDestinationsForUser(privateUser, 'new_message')
@@
         toUser={toUser}
         channelId={channelId}
-        /*unsubscribeUrl={unsubscribeUrl}*/
       />

Also applies to: 64-65

🤖 Prompt for AI Agents
In backend/email/emails/functions/helpers.tsx around lines 43-45 and 64-65,
remove the commented out unsubscribeUrl in the destructuring of
getNotificationDestinationsForUser (i.e., change "const { sendToEmail /*,
unsubscribeUrl*/ } = ..." to only include sendToEmail) so that there are no
leftover commented properties; update both occurrences accordingly and ensure
formatting/commas remain valid after removal.


const lover = await getLover(fromUser.id)
Expand All @@ -65,7 +61,7 @@ export const sendNewMessageEmail = async (
fromUserLover={lover}
toUser={toUser}
channelId={channelId}
unsubscribeUrl={unsubscribeUrl}
/*unsubscribeUrl={unsubscribeUrl}*/
/>
),
})
Expand All @@ -77,10 +73,8 @@ export const sendNewEndorsementEmail = async (
onUser: User,
text: string
) => {
const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
privateUser,
'new_endorsement'
)
const { sendToEmail /*, unsubscribeUrl*/ } =
getNotificationDestinationsForUser(privateUser, 'new_endorsement')
if (!privateUser.email || !sendToEmail) return
Comment on lines +76 to 78
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented unsubscribeUrl in sendNewEndorsementEmail

Same cleanup.

-  const { sendToEmail /*, unsubscribeUrl*/ } =
-    getNotificationDestinationsForUser(privateUser, 'new_endorsement')
+  const { sendToEmail } =
+    getNotificationDestinationsForUser(privateUser, 'new_endorsement')
@@
         onUser={onUser}
         endorsementText={text}
-        /*unsubscribeUrl={unsubscribeUrl}*/
       />

Also applies to: 89-90

🤖 Prompt for AI Agents
In backend/email/emails/functions/helpers.tsx around lines 76-78 (and also
remove the same commented reference at lines 89-90), remove the commented out
"unsubscribeUrl" from the destructuring of getNotificationDestinationsForUser so
the code only destructures active properties (e.g., const { sendToEmail } =
...), and delete the redundant commented fragment entirely to keep the codebase
clean.


return await sendEmail({
Expand All @@ -92,7 +86,7 @@ export const sendNewEndorsementEmail = async (
fromUser={fromUser}
onUser={onUser}
endorsementText={text}
unsubscribeUrl={unsubscribeUrl}
/*unsubscribeUrl={unsubscribeUrl}*/
/>
),
})
Expand Down
2 changes: 1 addition & 1 deletion backend/email/emails/functions/send-test-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ if (require.main === module) {
sendTestEmail(email)
.then(() => console.log('Email sent successfully!'))
.catch((error) => console.error('Failed to send email:', error))
}
}
27 changes: 14 additions & 13 deletions backend/email/emails/new-endorsement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Head,
Html,
Img,
Link,
//Link,
Preview,
Row,
Section,
Expand All @@ -20,18 +20,17 @@ interface NewEndorsementEmailProps {
fromUser: User
onUser: User
endorsementText: string
unsubscribeUrl: string
//unsubscribeUrl: string
}

export const NewEndorsementEmail = ({
fromUser,
onUser,
endorsementText,
unsubscribeUrl,
}: NewEndorsementEmailProps) => {
const name = onUser.name.split(' ')[0]
export const NewEndorsementEmail = (
newEndorsementEmailProps: NewEndorsementEmailProps
) => {
const onUser: User = newEndorsementEmailProps.onUser
const fromUser: User = newEndorsementEmailProps.fromUser
const name: string = onUser.name.split(' ')[0]

const endorsementUrl = `https://${DOMAIN}/${onUser.username}`
const endorsementUrl: string = `https://${DOMAIN}/${newEndorsementEmailProps.onUser.username}`

return (
<Html>
Expand Down Expand Up @@ -65,7 +64,9 @@ export const NewEndorsementEmail = ({
/>
</Column>
<Column>
<Text style={endorsementTextStyle}>"{endorsementText}"</Text>
<Text style={endorsementTextStyle}>
"{newEndorsementEmailProps.endorsementText}"
</Text>
</Column>
</Row>

Expand Down Expand Up @@ -172,9 +173,9 @@ const footerText = {
fontFamily: 'Ubuntu, Helvetica, Arial, sans-serif',
}

const footerLink = {
/*const footerLink = {
color: 'inherit',
textDecoration: 'none',
}
}*/

export default NewEndorsementEmail
21 changes: 9 additions & 12 deletions backend/email/emails/new-match.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,15 @@ interface NewMatchEmailProps {
onUser: User
matchedWithUser: User
matchedLover: LoverRow
unsubscribeUrl: string
//unsubscribeUrl: string
}

export const NewMatchEmail = ({
onUser,
matchedWithUser,
matchedLover,
unsubscribeUrl,
}: NewMatchEmailProps) => {
const name = onUser.name.split(' ')[0]
const userImgSrc = getLoveOgImageUrl(matchedWithUser, matchedLover)
const userUrl = `https://${DOMAIN}/${matchedWithUser.username}`
export const NewMatchEmail = (newMatchEmailProps: NewMatchEmailProps) => {
const name: string = newMatchEmailProps.onUser.name.split(' ')[0]
const matchedWithUser: User = newMatchEmailProps.matchedWithUser
const matchedLover: LoverRow = newMatchEmailProps.matchedLover
const userImgSrc: string = getLoveOgImageUrl(matchedWithUser, matchedLover)
const userUrl: string = `https://${DOMAIN}/${matchedWithUser.username}`

return (
<Html>
Expand Down Expand Up @@ -159,9 +156,9 @@ const footerText = {
fontFamily: 'Ubuntu, Helvetica, Arial, sans-serif',
}

const footerLink = {
/*const footerLink = {
color: 'inherit',
textDecoration: 'none',
}
}*/

export default NewMatchEmail
26 changes: 12 additions & 14 deletions backend/email/emails/new-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { type LoverRow } from 'common/love/lover'
import {
jamesLover,
jamesUser,
sinclairLover,
//sinclairLover,
sinclairUser,
} from './functions/mock'
import { DOMAIN } from 'common/envs/constants'
Expand All @@ -26,20 +26,18 @@ interface NewMessageEmailProps {
fromUserLover: LoverRow
toUser: User
channelId: number
unsubscribeUrl: string
//unsubscribeUrl: string
}

export const NewMessageEmail = ({
fromUser,
fromUserLover,
toUser,
channelId,
unsubscribeUrl,
}: NewMessageEmailProps) => {
const name = toUser.name.split(' ')[0]
const creatorName = fromUser.name
const messagesUrl = `https://${DOMAIN}/messages/${channelId}`
const userImgSrc = getLoveOgImageUrl(fromUser, fromUserLover)
export const NewMessageEmail = (newMessageEmailProps: NewMessageEmailProps) => {
const name: string = newMessageEmailProps.toUser.name.split(' ')[0]
const creatorName: string = newMessageEmailProps.fromUser.name
const channelId: number = newMessageEmailProps.channelId
const messagesUrl: string = `https://${DOMAIN}/messages/${channelId}`
const userImgSrc: string = getLoveOgImageUrl(
newMessageEmailProps.fromUser,
newMessageEmailProps.fromUserLover
)

return (
<Html>
Expand Down Expand Up @@ -98,7 +96,7 @@ NewMessageEmail.PreviewProps = {
fromUserLover: jamesLover,
toUser: sinclairUser,
channelId: 1,
unsubscribeUrl: 'https://manifold.love/unsubscribe',
//unsubscribeUrl: 'https://manifold.love/unsubscribe',
} as NewMessageEmailProps

const main = {
Expand Down
13 changes: 13 additions & 0 deletions backend/email/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
import tseslint from 'typescript-eslint'

/** @type {import('eslint').Linter.Config[]} */
export default [
{ ignores: ['lib/'] }, // generated by `tsc -b` so irrelevant (and all imports turn into requires there anyways)
{ files: ['**/*.{js,mjs,cjs,ts}'] },
{ languageOptions: { globals: globals.node } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{ rules: { '@typescript-eslint/no-unused-expressions': 'off' } },
]
6 changes: 5 additions & 1 deletion backend/email/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
"build": "tsc -b"
},
"dependencies": {
"@eslint/js": "9.32.0",
"@react-email/components": "0.0.33",
"eslint": "9.32.0",
"globals": "16.3.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-email": "3.0.7",
"resend": "4.1.2"
"resend": "4.1.2",
"typescript-eslint": "8.39.0"
Comment on lines +10 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Move ESLint dependencies to devDependencies.

ESLint and related tooling packages should be in devDependencies since they're only needed during development and CI, not at runtime.

   "dependencies": {
-    "@eslint/js": "9.32.0",
     "@react-email/components": "0.0.33",
-    "eslint": "9.32.0",
-    "globals": "16.3.0",
     "react": "19.0.0",
     "react-dom": "19.0.0",
     "react-email": "3.0.7",
     "resend": "4.1.2",
-    "typescript-eslint": "8.39.0"
   },
   "devDependencies": {
+    "@eslint/js": "9.32.0",
+    "eslint": "9.32.0",
+    "globals": "16.3.0",
     "@types/html-to-text": "9.0.4",
     "@types/prismjs": "1.26.5",
     "@types/react": "19.0.10",
-    "@types/react-dom": "19.0.4"
+    "@types/react-dom": "19.0.4",
+    "typescript-eslint": "8.39.0"
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@eslint/js": "9.32.0",
"@react-email/components": "0.0.33",
"eslint": "9.32.0",
"globals": "16.3.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-email": "3.0.7",
"resend": "4.1.2"
"resend": "4.1.2",
"typescript-eslint": "8.39.0"
"dependencies": {
"@react-email/components": "0.0.33",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-email": "3.0.7",
"resend": "4.1.2"
},
"devDependencies": {
"@eslint/js": "9.32.0",
"eslint": "9.32.0",
"globals": "16.3.0",
"@types/html-to-text": "9.0.4",
"@types/prismjs": "1.26.5",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"typescript-eslint": "8.39.0"
}
🤖 Prompt for AI Agents
In backend/email/package.json around lines 10 to 18, ESLint and related packages
are incorrectly listed under dependencies. Move "@eslint/js", "eslint", and
"typescript-eslint" from dependencies to devDependencies to ensure they are only
installed during development and CI, not in production runtime.

},
"devDependencies": {
"@types/html-to-text": "9.0.4",
Expand Down