@@ -18,6 +18,7 @@ import {
1818import { prepareHandlebars } from "../email/handlebarsHelpers"
1919import { Frequency } from "../auth/types"
2020
21+ const PROFILE_BATCH_SIZE = 50
2122const NUM_BILLS_TO_DISPLAY = 4
2223const NUM_USERS_TO_DISPLAY = 4
2324const NUM_TESTIMONIES_TO_DISPLAY = 6
@@ -45,89 +46,116 @@ const getVerifiedUserEmail = async (uid: string) => {
4546const deliverEmailNotifications = async ( ) => {
4647 const now = Timestamp . fromDate ( startOfDay ( new Date ( ) ) )
4748
48- console . log ( "Preparing handlebars helpers and partials" )
4949 prepareHandlebars ( )
50- console . log ( "Handlebars helpers and partials prepared" )
5150
52- const profilesSnapshot = await db
51+ let numProfilesProcessed = 0
52+
53+ let profilesSnapshot = await db
5354 . collection ( "profiles" )
5455 . where ( "nextDigestAt" , "<=" , now )
56+ . limit ( PROFILE_BATCH_SIZE )
5557 . get ( )
5658
57- console . log (
58- `Processing ${
59- profilesSnapshot . size
60- } profiles with nextDigestAt <= ${ now . toDate ( ) } `
61- )
59+ if ( profilesSnapshot . empty ) {
60+ console . log (
61+ `No profiles found with nextDigestAt <= ${ now . toDate ( ) } - sending 0 emails`
62+ )
63+ return
64+ }
6265
63- const emailPromises = profilesSnapshot . docs . map ( async profileDoc => {
64- const profile = profileDoc . data ( ) as Profile
65- if ( ! profile || ! profile . notificationFrequency ) {
66- console . log (
67- `User ${ profileDoc . id } has no notificationFrequency - skipping`
68- )
69- return
70- }
66+ do {
67+ console . log (
68+ `Processing batch of ${
69+ profilesSnapshot . size
70+ } profiles with nextDigestAt <= ${ now . toDate ( ) } starting with ${
71+ profilesSnapshot . docs [ 0 ] . id
72+ } `
73+ )
74+ numProfilesProcessed += profilesSnapshot . size
7175
72- // TODO: Temporarily using email from the profile to test the non-auth issues
73- // Should only use email from `auth` once that's working
74- const defaultEmail = profile . email || profile . contactInfo ?. publicEmail
75- const verifiedEmail =
76- ( await getVerifiedUserEmail ( profileDoc . id ) ) || defaultEmail
77- if ( ! verifiedEmail ) {
78- console . log (
79- `Skipping user ${ profileDoc . id } because they have no verified email address`
76+ const emailPromises = profilesSnapshot . docs . map ( async profileDoc => {
77+ const profile = profileDoc . data ( ) as Profile
78+ if (
79+ ! profile ||
80+ ! profile . notificationFrequency ||
81+ profile . notificationFrequency === "None"
82+ ) {
83+ console . log (
84+ `User ${ profileDoc . id } has no notificationFrequency - skipping`
85+ )
86+ return
87+ }
88+
89+ // TODO: Temporarily using email from the profile to test the non-auth issues
90+ // Should only use email from `auth` once that's working
91+ const defaultEmail = profile . email || profile . contactInfo ?. publicEmail
92+ const verifiedEmail =
93+ ( await getVerifiedUserEmail ( profileDoc . id ) ) || defaultEmail
94+ if ( ! verifiedEmail ) {
95+ console . log (
96+ `Skipping user ${ profileDoc . id } because they have no verified email address`
97+ )
98+ return
99+ }
100+
101+ const digestData = await buildDigestData (
102+ profileDoc . id ,
103+ now ,
104+ profile . notificationFrequency
80105 )
81- return
82- }
83106
84- const digestData = await buildDigestData (
85- profileDoc . id ,
86- now ,
87- profile . notificationFrequency
88- )
107+ const batch = db . batch ( )
89108
90- const batch = db . batch ( )
109+ // If there are no new notifications, don't send an email
110+ if (
111+ digestData . numBillsWithNewTestimony === 0 &&
112+ digestData . numUsersWithNewTestimony === 0
113+ ) {
114+ console . log (
115+ `No new notifications for ${ profileDoc . id } - not sending email`
116+ )
117+ } else {
118+ console . log (
119+ `Sending email to user ${ profileDoc . id } with data: ${ digestData } `
120+ )
121+ const htmlString = renderToHtmlString ( digestData )
91122
92- // If there are no new notifications, don't send an email
93- if (
94- digestData . numBillsWithNewTestimony === 0 &&
95- digestData . numUsersWithNewTestimony === 0
96- ) {
97- console . log (
98- `No new notifications for ${ profileDoc . id } - not sending email`
99- )
100- } else {
101- console . log (
102- `Sending email to user ${ profileDoc . id } with data: ${ digestData } `
103- )
104- const htmlString = renderToHtmlString ( digestData )
105-
106- const email = {
107- to : [ verifiedEmail ] ,
108- message : {
109- subject : "Your Notifications Digest" ,
110- text : convertHtmlToText ( htmlString ) , // TODO: Just make a text template for this
111- html : htmlString
112- } ,
113- createdAt : Timestamp . now ( )
123+ const email = {
124+ to : [ verifiedEmail ] ,
125+ message : {
126+ subject : "Your Notifications Digest" ,
127+ text : convertHtmlToText ( htmlString ) , // TODO: Just make a text template for this
128+ html : htmlString
129+ } ,
130+ createdAt : Timestamp . now ( )
131+ }
132+ batch . create ( db . collection ( "emails" ) . doc ( ) , email )
133+
134+ console . log ( `Saving email message to user ${ profileDoc . id } ` )
114135 }
115- batch . create ( db . collection ( "emails" ) . doc ( ) , email )
116136
117- console . log ( `Saving email message to user ${ profileDoc . id } ` )
118- }
137+ const nextDigestAt = getNextDigestAt ( profile . notificationFrequency )
138+ batch . update ( profileDoc . ref , { nextDigestAt } )
139+ await batch . commit ( )
140+
141+ console . log (
142+ `Updated nextDigestAt for ${ profileDoc . id } to ${ nextDigestAt ?. toDate ( ) } `
143+ )
144+ } )
119145
120- const nextDigestAt = getNextDigestAt ( profile . notificationFrequency )
121- batch . update ( profileDoc . ref , { nextDigestAt } )
122- await batch . commit ( )
146+ // Wait for all email documents to be created
147+ await Promise . all ( emailPromises )
123148
124- console . log (
125- `Updated nextDigestAt for ${ profileDoc . id } to ${ nextDigestAt ?. toDate ( ) } `
126- )
127- } )
149+ // Fetch the next batch of profiles
150+ profilesSnapshot = await db
151+ . collection ( "profiles" )
152+ . where ( "nextDigestAt" , "<=" , now )
153+ . startAfter ( profilesSnapshot . docs [ profilesSnapshot . docs . length - 1 ] )
154+ . limit ( PROFILE_BATCH_SIZE )
155+ . get ( )
156+ } while ( ! profilesSnapshot . empty )
128157
129- // Wait for all email documents to be created
130- await Promise . all ( emailPromises )
158+ console . log ( `Finished processing ${ numProfilesProcessed } profiles` )
131159}
132160
133161// TODO: Unit tests
@@ -239,6 +267,8 @@ const renderToHtmlString = (digestData: NotificationEmailDigest) => {
239267 path . join ( __dirname , EMAIL_TEMPLATE_PATH ) ,
240268 "utf8"
241269 )
270+ // TODO: Can we move the compilation up so we only compile the template
271+ // once per job run instead of once per email?
242272 const compiledTemplate = handlebars . compile ( templateSource )
243273 return compiledTemplate ( digestData )
244274}
0 commit comments