Skip to content
Draft
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
34 changes: 16 additions & 18 deletions docs/monitor-emails.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,43 @@ The different types of templates for emails are located in `src/views/emails`. D
### Email verification

We send this email to users in order to verify that is indeed them that are adding their email address to be monitored.
- User adds a new email address to be monitored
- We send the user an email with a link to verify and add their email

- User adds a new email address to be monitored
- We send the user an email with a link to verify and add their email

### Breach notification

The breach notification alerts users if we detected that their email address was involved in a breach. Depending on the user's preferences that they can choose on the settings page: This email is sent either to the email address that has been involved in the breach or to the primary address they signed up with.
- HIBP received a new breach that is associated with one of the user's email addresses
- `/notify` endpoint is called by HIBP
- We send out an email to the user listing details on the breach.

### Monthly unresolved breaches

A monthly email that prompts the user with an overview of their unresolved breaches if any.
- Lists unresolved breaches for monitored email addresses
- The email is sent out on a monthly cadence
- HIBP received a new breach that is associated with one of the user's email addresses
- `/notify` endpoint is called by HIBP
- We send out an email to the user listing details on the breach.

### Sign-up report

Upon sign up, we scan the user's primary email for breaches and send them a report of the test result.
- When a user signs up for Monitor we scan their primary email address for breaches
- We send out a report using one of two templates:
- No breaches found
- Report of breaches involved

- When a user signs up for Monitor we scan their primary email address for breaches
- We send out a report using one of two templates:
- No breaches found
- Report of breaches involved

## Email previewer

For viewing, developing, and sending Monitor email templates we added an email previewer. There are two versions:
- Non-auth preview `/preview/emails`: It aims to be accessible by people outside of our team and does not require authentication.
- Admin `/admin/emails`: This version is not only to authenticate into Monitor but also is restricted to team members that have admin rights. This version lets admins send emails for testing purposes.

- Non-auth preview `/preview/emails`: It aims to be accessible by people outside of our team and does not require authentication.
- Admin `/admin/emails`: This version is not only to authenticate into Monitor but also is restricted to team members that have admin rights. This version lets admins send emails for testing purposes.

Both are currently only available for `dev` and `stage` environments.

### Non-auth preview

The *non-auth preview* is reachable under `/preview/emails`. This is a view-only version of the email templates.
The _non-auth preview_ is reachable under `/preview/emails`. This is a view-only version of the email templates.

### Admin preview

The *admin preview* is reachable under `/admin/emails`. It has one addition to the *non-auth preview* and comes with the ability to send emails. For more info see [Sending emails](#sending-emails).
The _admin preview_ is reachable under `/admin/emails`. It has one addition to the _non-auth preview_ and comes with the ability to send emails. For more info see [Sending emails](#sending-emails).

### Templates

Expand Down
10 changes: 0 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,10 @@
"type": "module",
"scripts": {
"dev": "npm run build-nimbus && next dev --turbo --port=6060",
"dev:cron:first-data-broker-removal-fixed": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx",
"dev:cron:monthly-activity-free": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/monthlyActivityFree.tsx",
"dev:cron:monthly-activity-plus": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/monthlyActivityPlus.tsx",
"dev:cron:breach-alerts": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/emailBreachAlerts.tsx",
"dev:cron:db-delete-unverified-subscribers": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/deleteUnverifiedSubscribers.ts",
"dev:cron:db-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/syncBreaches.ts",
"dev:cron:db-pull-data-brokers": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/syncOnerepDataBrokers.ts",
"dev:cron:remote-settings-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/updateBreachesInRemoteSettings/index.ts",
"dev:cron:onerep-limits-alert": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/onerepStatsAlert.ts",
"dev:cron:report-lighthouse-results": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/reportLighthouseResults.ts",
"dev:nimbus": "node --watch-path config/nimbus.yaml src/scripts/build/nimbusTypes.js",
"build": "npm run get-location-data && npm run build-glean && npm run build-nimbus && next build && npm run build-cronjobs",
Expand All @@ -29,15 +24,10 @@
"test-integrations": "npm run build-nimbus && jest \"\\.integration\\.ts\" --coverage=false --runInBand",
"functional-tests": "playwright test functional-tests/ --config=functional-tests/playwright.config.ts",
"functional-tests:debug": "playwright test functional-tests/ --config=functional-tests/playwright.config.ts --ui",
"cron:first-data-broker-removal-fixed": "node dist/scripts/cronjobs/firstDataBrokerRemovalFixed.js",
"cron:monthly-activity-free": "node dist/scripts/cronjobs/monthlyActivityFree.js",
"cron:monthly-activity-plus": "node dist/scripts/cronjobs/monthlyActivityPlus.js",
"cron:breach-alerts": "node dist/scripts/cronjobs/emailBreachAlerts.js",
"cron:db-delete-unverified-subscribers": "node dist/scripts/cronjobs/deleteUnverifiedSubscribers.js",
"cron:db-pull-breaches": "node dist/scripts/cronjobs/syncBreaches.js",
"cron:db-pull-data-brokers": "node dist/scripts/cronjobs/syncOnerepDataBrokers.js",
"cron:remote-settings-pull-breaches": "node dist/scripts/cronjobs/updateBreachesInRemoteSettings/index.js",
"cron:onerep-limits-alert": "node dist/scripts/cronjobs/onerepStatsAlert.js",
"cron:report-lighthouse-results": "node dist/scripts/cronjobs/reportLighthouseResults.js",
"db:migrate": "node -r dotenv-flow/config node_modules/knex/bin/cli.js migrate:latest --knexfile src/db/knexfile.js",
"db:rollback": "node -r dotenv-flow/config node_modules/knex/bin/cli.js migrate:rollback --knexfile src/db/knexfile.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import Link from "next/link";
import styles from "./EmailTrigger.module.scss";
import {
triggerBreachAlert,
triggerFirstDataBrokerRemovalFixed,
triggerMonthlyActivityFree,
triggerMonthlyActivityPlus,
triggerSignupReportEmail,
triggerVerificationEmail,
} from "./actions";
Expand All @@ -28,16 +25,6 @@ export const EmailTrigger = (props: Props) => {
const [isSendingSignupReport, setIsSendingSignupReport] = useState(false);
const [isSendingVerification, setIsSendingVerification] = useState(false);
const [isSendingBreachAlert, setIsSendingBreachAlert] = useState(false);
const [
isSendingMonthlyActivityFreeOverview,
setIsSendingMonthlyActivityFreeOverview,
] = useState(false);
const [
isSendingMonthlyActivityPlusOverview,
setIsSendingMonthlyActivityPlusOverview,
] = useState(false);
const [firstDataBrokerRemovalFixed, setFirstDataBrokerRemovalFixed] =
useState(false);

return (
<main className={styles.wrapper}>
Expand Down Expand Up @@ -89,30 +76,6 @@ export const EmailTrigger = (props: Props) => {
>
Verify email address
</Button>
<Button
variant="primary"
isLoading={isSendingMonthlyActivityFreeOverview}
onPress={() => {
setIsSendingMonthlyActivityFreeOverview(true);
void triggerMonthlyActivityFree(selectedEmailAddress).then(() => {
setIsSendingMonthlyActivityFreeOverview(false);
});
}}
>
Monthly activity overview (free)
</Button>
<Button
variant="primary"
isLoading={isSendingMonthlyActivityPlusOverview}
onPress={() => {
setIsSendingMonthlyActivityPlusOverview(true);
void triggerMonthlyActivityPlus(selectedEmailAddress).then(() => {
setIsSendingMonthlyActivityPlusOverview(false);
});
}}
>
Monthly activity overview (Plus)
</Button>
<Button
variant="primary"
isLoading={isSendingBreachAlert}
Expand All @@ -125,20 +88,6 @@ export const EmailTrigger = (props: Props) => {
>
Breach alert
</Button>
<Button
variant="primary"
isLoading={firstDataBrokerRemovalFixed}
onPress={() => {
setFirstDataBrokerRemovalFixed(true);
void triggerFirstDataBrokerRemovalFixed(selectedEmailAddress).then(
() => {
setFirstDataBrokerRemovalFixed(false);
},
);
}}
>
First data broker removal fixed
</Button>
</div>
</main>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,9 @@ import { getSubscriberByFxaUid } from "../../../../../../db/tables/subscribers";
import { ReactNode } from "react";
import { SubscriberRow } from "knex/types/tables";
import { getUserEmails } from "../../../../../../db/tables/emailAddresses";
import { MonthlyActivityPlusEmail } from "../../../../../../emails/templates/monthlyActivityPlus/MonthlyActivityPlusEmail";
import { getDashboardSummary } from "../../../../../functions/server/dashboard";
import { getSubscriberBreaches } from "../../../../../functions/server/getSubscriberBreaches";
import { getCountryCode } from "../../../../../functions/server/getCountryCode";
import { headers } from "next/headers";
import { FirstDataBrokerRemovalFixed } from "../../../../../../emails/templates/firstDataBrokerRemovalFixed/FirstDataBrokerRemovalFixed";
import {
createRandomHibpListing,
createRandomScanResult,
} from "../../../../../../apiMocks/mockData";
import { createRandomHibpListing } from "../../../../../../apiMocks/mockData";
import { BreachAlertEmail } from "../../../../../../emails/templates/breachAlert/BreachAlertEmail";
import { SignupReportEmail } from "../../../../../../emails/templates/signupReport/SignupReportEmail";
import { getBreachesForEmail } from "../../../../../../utils/hibp";
Expand All @@ -37,10 +30,7 @@ import { getSignupLocaleCountry } from "../../../../../../emails/functions/getSi
import { refreshStoredScanResults } from "../../../../../functions/server/refreshStoredScanResults";
import { hasPremium } from "../../../../../functions/universal/user";
import { isEligibleForPremium } from "../../../../../functions/universal/premium";
import { MonthlyActivityFreeEmail } from "../../../../../../emails/templates/monthlyActivityFree/MonthlyActivityFreeEmail";
import { getMonthlyActivityFreeUnsubscribeLink } from "../../../../../../app/functions/cronjobs/unsubscribeLinks";
import { getScanResultsWithBroker } from "../../../../../../db/tables/onerep_scans";
import { getEnabledFeatureFlags } from "../../../../../../db/tables/featureFlags";
import { getExperimentationIdFromUserSession } from "../../../../../functions/server/getExperimentationId";
import { getExperiments } from "../../../../../functions/server/getExperiments";
import { getLocale } from "../../../../../functions/universal/getLocale";
Expand Down Expand Up @@ -136,98 +126,6 @@ export async function triggerVerificationEmail(emailAddress: string) {
);
}

export async function triggerMonthlyActivityFree(emailAddress: string) {
const session = await getServerSession();
const subscriber = await getAdminSubscriber();
if (!subscriber || !session?.user) {
return false;
}

const acceptLangHeader = await getAcceptLangHeaderInServerComponents();
const l10n = getL10n(acceptLangHeader);

if (typeof subscriber.onerep_profile_id === "number") {
await refreshStoredScanResults(subscriber.onerep_profile_id);
}

const enabledFeatureFlags = await getEnabledFeatureFlags({
email: subscriber.primary_email,
});
const countryCode = getCountryCode(await headers());
const experimentationId = await getExperimentationIdFromUserSession(
session.user,
);
const experimentData = await getExperiments({
experimentationId,
countryCode,
locale: getLocale(l10n),
});
const latestScan = await getScanResultsWithBroker(
subscriber.onerep_profile_id,
hasPremium(session.user),
);
const data = getDashboardSummary(
latestScan.results,
await getSubscriberBreaches({
fxaUid: session.user.subscriber?.fxa_uid,
countryCode,
}),
);

const unsubscribeLink =
await getMonthlyActivityFreeUnsubscribeLink(subscriber);

await send(
emailAddress,
l10n.getString("email-monthly-free-subject"),
<MonthlyActivityFreeEmail
subscriber={sanitizeSubscriberRow(subscriber)}
l10n={l10n}
dataSummary={data}
unsubscribeLink={unsubscribeLink as string}
enabledFeatureFlags={enabledFeatureFlags}
experimentData={experimentData["Features"]}
/>,
);
}

export async function triggerMonthlyActivityPlus(emailAddress: string) {
const session = await getServerSession();
const subscriber = await getAdminSubscriber();
if (!subscriber || !session?.user) {
return false;
}

const acceptLangHeader = await getAcceptLangHeaderInServerComponents();
const l10n = getL10n(acceptLangHeader);

if (typeof subscriber.onerep_profile_id === "number") {
await refreshStoredScanResults(subscriber.onerep_profile_id);
}
const countryCode = getCountryCode(await headers());
const latestScan = await getScanResultsWithBroker(
subscriber.onerep_profile_id,
hasPremium(session.user),
);
const data = getDashboardSummary(
latestScan.results,
await getSubscriberBreaches({
fxaUid: session.user.subscriber?.fxa_uid,
countryCode,
}),
);

await send(
emailAddress,
l10n.getString("email-monthly-plus-auto-subject"),
<MonthlyActivityPlusEmail
subscriber={sanitizeSubscriberRow(subscriber)}
l10n={l10n}
data={data}
/>,
);
}

export async function triggerBreachAlert(emailAddress: string) {
const session = await getServerSession();
const subscriber = await getAdminSubscriber();
Expand Down Expand Up @@ -278,22 +176,3 @@ export async function triggerBreachAlert(emailAddress: string) {
/>,
);
}

export async function triggerFirstDataBrokerRemovalFixed(emailAddress: string) {
const acceptLangHeader = await getAcceptLangHeaderInServerComponents();
const l10n = getL10n(acceptLangHeader);
const randomScanResult = createRandomScanResult({ status: "removed" });

await send(
emailAddress,
l10n.getString("email-first-broker-removal-fixed-subject"),
<FirstDataBrokerRemovalFixed
data={{
dataBrokerName: randomScanResult.data_broker,
dataBrokerLink: `${process.env.SERVER_URL}/user/dashboard/fixed`,
removalDate: randomScanResult.updated_at,
}}
l10n={l10n}
/>,
);
}

This file was deleted.

This file was deleted.

Loading
Loading