@@ -18,6 +18,7 @@ import {
18
18
import { prepareHandlebars } from "../email/handlebarsHelpers"
19
19
import { Frequency } from "../auth/types"
20
20
21
+ const PROFILE_BATCH_SIZE = 50
21
22
const NUM_BILLS_TO_DISPLAY = 4
22
23
const NUM_USERS_TO_DISPLAY = 4
23
24
const NUM_TESTIMONIES_TO_DISPLAY = 6
@@ -45,89 +46,116 @@ const getVerifiedUserEmail = async (uid: string) => {
45
46
const deliverEmailNotifications = async ( ) => {
46
47
const now = Timestamp . fromDate ( startOfDay ( new Date ( ) ) )
47
48
48
- console . log ( "Preparing handlebars helpers and partials" )
49
49
prepareHandlebars ( )
50
- console . log ( "Handlebars helpers and partials prepared" )
51
50
52
- const profilesSnapshot = await db
51
+ let numProfilesProcessed = 0
52
+
53
+ let profilesSnapshot = await db
53
54
. collection ( "profiles" )
54
55
. where ( "nextDigestAt" , "<=" , now )
56
+ . limit ( PROFILE_BATCH_SIZE )
55
57
. get ( )
56
58
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
+ }
62
65
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
71
75
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
80
105
)
81
- return
82
- }
83
106
84
- const digestData = await buildDigestData (
85
- profileDoc . id ,
86
- now ,
87
- profile . notificationFrequency
88
- )
107
+ const batch = db . batch ( )
89
108
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 )
91
122
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 } ` )
114
135
}
115
- batch . create ( db . collection ( "emails" ) . doc ( ) , email )
116
136
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
+ } )
119
145
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 )
123
148
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 )
128
157
129
- // Wait for all email documents to be created
130
- await Promise . all ( emailPromises )
158
+ console . log ( `Finished processing ${ numProfilesProcessed } profiles` )
131
159
}
132
160
133
161
// TODO: Unit tests
@@ -239,6 +267,8 @@ const renderToHtmlString = (digestData: NotificationEmailDigest) => {
239
267
path . join ( __dirname , EMAIL_TEMPLATE_PATH ) ,
240
268
"utf8"
241
269
)
270
+ // TODO: Can we move the compilation up so we only compile the template
271
+ // once per job run instead of once per email?
242
272
const compiledTemplate = handlebars . compile ( templateSource )
243
273
return compiledTemplate ( digestData )
244
274
}
0 commit comments