diff --git a/backend/email/emails/functions/mock.ts b/backend/email/emails/functions/mock.ts index 234e33b8..7272a98c 100644 --- a/backend/email/emails/functions/mock.ts +++ b/backend/email/emails/functions/mock.ts @@ -17,9 +17,11 @@ export type PartialProfile = Pick< > & Partial +const avatarUrl = + 'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/user-images%2FMartin%2Flove-images%2FeD09lRtuRF.jpg?alt=media&token=db41b5f2-b1c0-4df9-aa9c-11d44c970947' export const mockUser: User = { createdTime: 0, - avatarUrl: 'https://martinbraquet.com/wp-content/uploads/BDo_Tbzj.jpeg', + avatarUrl: avatarUrl, id: '0k1suGSJKVUnHbCPEhHNpgZPkUP2', username: 'Martin', name: 'Martin', @@ -27,7 +29,7 @@ export const mockUser: User = { export const jamesUser: User = { createdTime: 0, - avatarUrl: 'https://martinbraquet.com/wp-content/uploads/BDo_Tbzj.jpeg', + avatarUrl: avatarUrl, id: '5LZ4LgYuySdL1huCWe7bti02ghx2', username: 'James', name: 'James', diff --git a/backend/email/emails/new-message.tsx b/backend/email/emails/new-message.tsx index df9ca041..86fb1b85 100644 --- a/backend/email/emails/new-message.tsx +++ b/backend/email/emails/new-message.tsx @@ -4,6 +4,7 @@ import { Container, Head, Html, + Img, Link, Preview, Section, @@ -13,7 +14,7 @@ import {ANDROID_APP_URL} from 'common/constants' import {DOMAIN} from 'common/envs/constants' import {type ProfileRow} from 'common/profiles/profile' import {type User} from 'common/user' -import {container, content, Footer, main} from 'email/utils' +import {container, content, DARK_MODE_CSS, Footer, main} from 'email/utils' import React from 'react' import {createT} from 'shared/locale' @@ -31,7 +32,7 @@ interface NewMessageEmailProps { export const NewMessageEmail = ({ fromUser, - // fromUserProfile, + fromUserProfile, toUser, channelId, unsubscribeUrl, @@ -41,16 +42,25 @@ export const NewMessageEmail = ({ const name = toUser.name.split(' ')[0] const creatorName = fromUser.name const messagesUrl = `https://${DOMAIN}/messages/${channelId}` - // const userImgSrc = getOgImageUrl(fromUser, fromUserProfile) + const profileUrl = `https://${DOMAIN}/${fromUser.username}` + // Every client renders reliably, none of them render a CSS-generated placeholder — so an absent + // avatar falls back to the same asset the web app uses rather than to a broken image icon. + const avatarUrl = fromUser.avatarUrl const t = createT(locale) + // Age and city are the two facts a reader can act on at a glance; anything more turns the card into a + // profile page and competes with the one button this email has. + const meta = [fromUserProfile?.age, fromUserProfile?.city].filter(Boolean).join(' · ') + return ( - + + + {t('email.new_message.preview', 'New message from {creatorName}', {creatorName})} - + {/*
*/} {/* */} {/*
*/} -
+
+ {/* A pill rather than a headline: it names the event in one glance, above the fold and above + the greeting, which is what the reader is scanning for when the subject line already + said someone wrote to them. */} + + {t('email.new_message.badge', 'New message')} + + {t('email.new_message.greeting', 'Hi {name},', {name})}
+ {/* The avatar is the whole point of the redesign: a face converts where a name does not. + The ring is a padded round div rather than a box-shadow, which Gmail strips. */} + {avatarUrl && ( + + + {creatorName} + + + )} + + {creatorName} + + + - {t('email.new_message.message', '{creatorName} just messaged you!', {creatorName})} + @{fromUser.username} + {meta ? ` · ${meta}` : ''} + {/* A chat bubble, not a paragraph: the asymmetric corner reads as a message in every client + without needing a CSS triangle that Outlook would drop. */} +
+ + {t('email.new_message.message', '{creatorName} just messaged you!', { + creatorName, + })} + +
+ + + + + {t('email.new_message.view_profile', 'View profile')} + +
+ {/* The nudge sits outside the card so the card stays a single, clean unit — and because the + reader who is going to reply has already clicked by the time they reach this line. */} + {t( + 'email.new_message.nudge', + 'One sentence back is usually all it takes to start something real.', + )} + + +
+ + diff --git a/common/messages/de.json b/common/messages/de.json index 487e5c0a..213a0bfb 100644 --- a/common/messages/de.json +++ b/common/messages/de.json @@ -314,7 +314,7 @@ "email.new_message.message": "{creatorName} hat Ihnen gerade eine Nachricht geschickt!", "email.new_message.preview": "Neue Nachricht von {creatorName}", "email.new_message.subject": "{creatorName} hat Ihnen eine Nachricht geschickt!", - "email.new_message.viewButton": "Nachricht anzeigen", + "email.new_message.viewButton": "Lesen und antworten", "email.search_alerts.callToAction": "Wenn jemand zu Ihnen passt, treten Sie in Kontakt. Ein einfaches Hallo kann der Beginn einer bedeutungsvollen Freundschaft, Zusammenarbeit oder Beziehung sein.", "email.search_alerts.communityNote": "Compass wird von der Community aufgebaut und erhalten — keine Werbung, keine versteckten Algorithmen, keine Abonnements. Ihre Präsenz und Teilnahme machen es möglich.", "email.search_alerts.greeting": "Hallo {name},", @@ -1749,6 +1749,9 @@ "email.new_message.daily_limit": "Um deine Inbox nicht zu überladen, erhältst du höchstens eine E-Mail pro Tag pro Konversation. Wenn {creatorName} heute weitere Nachrichten sendet, wirst du erst morgen wieder per E-Mail benachrichtigt.", "email.new_message.app_prompt": "Willst du Echtzeit-Benachrichtigungen? Lade die Compass App für Android herunter:", "email.new_message.app_link": "App herunterladen", + "email.new_message.badge": "Neue Nachricht", + "email.new_message.view_profile": "Profil ansehen", + "email.new_message.nudge": "Ein einziger Satz zurück reicht oft, damit etwas Echtes entsteht.", "faq.empty.cta": "Frage stellen →", "faq.empty.text": "Versuche ein allgemeineres Wort — oder frag uns direkt, wir beantworten jede Nachricht.", "faq.empty.title": "Dazu passt hier nichts.", diff --git a/common/messages/fr.json b/common/messages/fr.json index 89057092..489de56a 100644 --- a/common/messages/fr.json +++ b/common/messages/fr.json @@ -314,7 +314,7 @@ "email.new_message.message": "{creatorName} vient de vous envoyer un message !", "email.new_message.preview": "Nouveau message de {creatorName}", "email.new_message.subject": "{creatorName} vous a envoyé un message !", - "email.new_message.viewButton": "Voir le message", + "email.new_message.viewButton": "Lire et répondre", "email.search_alerts.callToAction": "Si quelqu'un vous parle, contactez-les. Un simple bonjour peut être le début d'une amitié, d'une collaboration ou d'une relation significative.", "email.search_alerts.communityNote": "Compass est construit et soutenu par la communauté — pas de publicité, pas d'algorithmes cachés, pas d'abonnements. Votre présence et votre participation le rendent possible.", "email.search_alerts.greeting": "Bonjour {name},", @@ -1748,6 +1748,9 @@ "email.new_message.daily_limit": "Pour éviter de surcharger votre boîte de réception, vous recevrez au plus un e-mail par jour par conversation. Si {creatorName} envoie d'autres messages aujourd'hui, vous ne serez pas notifié par e-mail.", "email.new_message.app_prompt": "Vous voulez des notifications en temps réel ? Téléchargez l'application Compass pour Android :", "email.new_message.app_link": "Obtenir l'app", + "email.new_message.badge": "Nouveau message", + "email.new_message.view_profile": "Voir le profil", + "email.new_message.nudge": "Une seule phrase en retour suffit souvent à faire naître quelque chose de vrai.", "faq.empty.cta": "Poser une question →", "faq.empty.text": "Essayez un mot plus général — ou posez-nous directement la question, nous répondons à chaque message.", "faq.empty.title": "Aucun résultat pour cette recherche.",