diff --git a/packages/server/__tests__/db/seeds/fixtures.js b/packages/server/__tests__/db/seeds/fixtures.js index 9672afb24..7b9e2c5cf 100644 --- a/packages/server/__tests__/db/seeds/fixtures.js +++ b/packages/server/__tests__/db/seeds/fixtures.js @@ -248,7 +248,7 @@ const grants = { search_terms: '[in title/desc]+', reviewer_name: 'none', opportunity_category: 'Discretionary', - description: '', + description: null, eligibility_codes: '', opportunity_status: 'posted', raw_body_json: { diff --git a/packages/server/__tests__/email/email.test.js b/packages/server/__tests__/email/email.test.js index ee31fab54..5d4e5f3d4 100644 --- a/packages/server/__tests__/email/email.test.js +++ b/packages/server/__tests__/email/email.test.js @@ -258,6 +258,16 @@ describe('Email sender', () => { expect(sendFake.secondCall.args[0].emailHTML.includes(' { + const sendFake = sinon.fake.returns('foo'); + sinon.replace(email, 'sendGrantAssignedNotficationForAgency', sendFake); + const grant = fixtures.grants.noDescOrEligibilityCodes; + + await email.sendGrantAssignedEmail({ grantId: grant.grant_id, agencyIds: [0], userId: 1 }); + + expect(sendFake.called).to.equal(true); + expect(sendFake.firstCall.args[1].includes('... View on')).to.equal(true); + }); }); context('async report email', () => { it('sendAsyncReportEmail delivers an email with the signedURL for audit report', async () => { diff --git a/packages/server/src/lib/email.js b/packages/server/src/lib/email.js index 9570a8d3a..320611d3d 100644 --- a/packages/server/src/lib/email.js +++ b/packages/server/src/lib/email.js @@ -5,6 +5,7 @@ const asyncBatch = require('async-batch').default; const fileSystem = require('fs'); const path = require('path'); const mustache = require('mustache'); +const { log } = require('./logging'); const emailService = require('./email/service-email'); const db = require('../db'); const { notificationType } = require('./email/constants'); @@ -177,7 +178,7 @@ function buildGrantsUrlSafe(emailNotificationType) { function getGrantDetail(grant, emailNotificationType) { const grantDetailTemplate = fileSystem.readFileSync(path.join(__dirname, '../static/email_templates/_grant_detail.html')); - const description = grant.description.substring(0, 380).replace(/(<([^>]+)>)/ig, ''); + const description = grant.description?.substring(0, 380).replace(/(<([^>]+)>)/ig, ''); const grantDetail = mustache.render( grantDetailTemplate.toString(), { title: grant.title, @@ -258,9 +259,20 @@ async function sendGrantAssignedEmail({ grantId, agencyIds, userId }) { 2b. For each user part of the agency i. Send email */ - const grantDetail = await buildGrantDetail(grantId, notificationType.grantAssignment); - const agencies = await db.getAgenciesByIds(agencyIds); - agencies.forEach((agency) => module.exports.sendGrantAssignedNotficationForAgency(agency, grantDetail, userId)); + try { + const grantDetail = await buildGrantDetail(grantId, notificationType.grantAssignment); + const agencies = await db.getAgenciesByIds(agencyIds); + await asyncBatch( + agencies, + (agency) => { module.exports.sendGrantAssignedNotficationForAgency(agency, grantDetail, userId); }, + 2, + ); + } catch (err) { + log.error({ + err, grantId, agencyIds, userId, + }, 'Failed to send grant assigned email'); + throw err; + } } async function buildDigestBody({ name, openDate, matchedGrants }) { diff --git a/packages/server/src/routes/grants.js b/packages/server/src/routes/grants.js index eda8321ee..fcb81502c 100755 --- a/packages/server/src/routes/grants.js +++ b/packages/server/src/routes/grants.js @@ -360,7 +360,12 @@ router.put('/:grantId/assign/agencies', requireUser, async (req, res) => { } await db.assignGrantsToAgencies({ grantId, agencyIds, userId: user.id }); - email.sendGrantAssignedEmail({ grantId, agencyIds, userId: user.id }); + try { + await email.sendGrantAssignedEmail({ grantId, agencyIds, userId: user.id }); + } catch { + res.sendStatus(500); + return; + } res.json({}); });