From 5f0ecaa91c15691eb61de1e3944624e33734b789 Mon Sep 17 00:00:00 2001 From: Anis Safine Laget <anis.safine@beta.gouv.fr> Date: Mon, 24 Mar 2025 10:53:53 +0100 Subject: [PATCH 1/4] =?UTF-8?q?fix(mail):=20envoie=20les=20mails=20group?= =?UTF-8?q?=C3=A9s=20en=20bcc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/src/tools/api-mailjet/emails.ts | 4 ++-- packages/api/src/tools/api-mailjet/index.ts | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/api/src/tools/api-mailjet/emails.ts b/packages/api/src/tools/api-mailjet/emails.ts index a292a0a3e..3e3525e20 100644 --- a/packages/api/src/tools/api-mailjet/emails.ts +++ b/packages/api/src/tools/api-mailjet/emails.ts @@ -5,7 +5,7 @@ import { emailCheck } from '../email-check' import { config } from '../../config/index' import { isNotNullNorUndefined, OmitDistributive, onlyUnique } from 'camino-common/src/typescript-tools' -export const mailjetSend = async (emails: readonly string[], message: OmitDistributive<CaminoMailMessage, 'To'>): Promise<void> => { +export const mailjetSend = async (emails: readonly string[], message: OmitDistributive<CaminoMailMessage, 'To' | 'Bcc'>): Promise<void> => { try { if (!Array.isArray(emails)) { throw new Error(`un tableau d'emails est attendu ${emails}`) @@ -27,7 +27,7 @@ export const mailjetSend = async (emails: readonly string[], message: OmitDistri const sendTo: MailjetPostMessageRecipient[] = emails.map(Email => ({ Email, Name: Email })) const fullMessage: CaminoMailMessage = { ...message, - To: sendTo, + ...(sendTo.length > 1 ? { Bcc: sendTo } : { To: sendTo }), } await mailJetSendMail(fullMessage) } catch (e: any) { diff --git a/packages/api/src/tools/api-mailjet/index.ts b/packages/api/src/tools/api-mailjet/index.ts index ace303456..9d355910c 100644 --- a/packages/api/src/tools/api-mailjet/index.ts +++ b/packages/api/src/tools/api-mailjet/index.ts @@ -7,11 +7,19 @@ export interface MailjetPostMessageRecipient { Email: string Name: string } -interface MailjetPostMessageCommon { +type MailjetPostRecipients = + | { + To: MailjetPostMessageRecipient[] + Bcc?: MailjetPostMessageRecipient[] + } + | { + To?: MailjetPostMessageRecipient[] + Bcc: MailjetPostMessageRecipient[] + } +type MailjetPostMessageCommon = { From: MailjetPostMessageRecipient ReplyTo: Omit<MailjetPostMessageRecipient, 'Name'> - To: MailjetPostMessageRecipient[] -} +} & MailjetPostRecipients interface MailjetPostBodyMessage { Subject: string @@ -24,7 +32,7 @@ interface MailjetPostTemplateMessage { Variables: Record<string, string> } -export type CaminoMailMessage = { To: MailjetPostMessageRecipient[] } & (MailjetPostBodyMessage | MailjetPostTemplateMessage) +export type CaminoMailMessage = MailjetPostRecipients & (MailjetPostBodyMessage | MailjetPostTemplateMessage) type MailjetPostMessage = MailjetPostMessageCommon & (MailjetPostBodyMessage | MailjetPostTemplateMessage) interface MailjetPost { @@ -114,7 +122,8 @@ export const mailJetSendMail = async (post: CaminoMailMessage): Promise<void> => if (values.Messages.find(message => message.Status !== 'success')) { console.warn(`Quelque chose s'est mal passé durant l'envoi des mails, réponse: ${JSON.stringify(values)}`) } else { - console.info(`Messages envoyés: ${post.To.map(({ Email }) => Email).join(', ')}, MessageIDs: ${values.Messages.flatMap(m => m.To.flatMap(to => to.MessageID)).join(', ')}`) + const recipients = [...(post.To ?? []), ...(post.Bcc ?? [])] + console.info(`Messages envoyés: ${recipients.map(({ Email }) => Email).join(', ')}, MessageIDs: ${values.Messages.flatMap(m => m.To.flatMap(to => to.MessageID)).join(', ')}`) } } else { console.error(`Une erreur est survenue lors de l'envoi des mails ${await result.text()}`) -- GitLab From 33dd46480698a27c5ba9bad44fe1edd653eea9ac Mon Sep 17 00:00:00 2001 From: Anis Safine Laget <anis.safine@beta.gouv.fr> Date: Mon, 24 Mar 2025 16:04:58 +0100 Subject: [PATCH 2/4] =?UTF-8?q?am=C3=A9lioration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../titres-activites-relance-send.ts | 20 +++++----- packages/api/src/tools/api-mailjet/emails.ts | 37 ++++++++++++++++--- packages/api/src/tools/api-mailjet/index.ts | 6 +-- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/packages/api/src/business/processes/titres-activites-relance-send.ts b/packages/api/src/business/processes/titres-activites-relance-send.ts index ba5df1285..6bfaac024 100644 --- a/packages/api/src/business/processes/titres-activites-relance-send.ts +++ b/packages/api/src/business/processes/titres-activites-relance-send.ts @@ -45,7 +45,7 @@ export const checkDateAndSendEmail = async ( const dateDelai = dateAddDays(aujourdhui, ACTIVITES_DELAI_RELANCE_JOURS) const titresActivitesRelanceToSend = activites.filter(({ date }) => dateDelai === dateAddMonths(date, 3)) - if (titresActivitesRelanceToSend.length) { + if (isNotNullNorUndefinedNorEmpty(titresActivitesRelanceToSend)) { // envoi d’email aux opérateurs pour les relancer ACTIVITES_DELAI_RELANCE_JOURS jours avant la fermeture automatique de l’activité const emails = new Set<string>() for (const activite of titresActivitesRelanceToSend) { @@ -60,16 +60,16 @@ export const checkDateAndSendEmail = async ( } }) } + } - if (emails.size) { - await emailsWithTemplateSend([...emails], EmailTemplateId.ACTIVITES_RELANCE, { - activitesUrl: activitesUrlGet({ - activiteTypesIds, - activiteStatutsIds, - annees: [anneePrecedente(getAnnee(aujourdhui))], - }), - }) - } + if (emails.size > 0) { + await emailsWithTemplateSend([...emails], EmailTemplateId.ACTIVITES_RELANCE, { + activitesUrl: activitesUrlGet({ + activiteTypesIds, + activiteStatutsIds, + annees: [anneePrecedente(getAnnee(aujourdhui))], + }), + }) } console.info('titre / activités (relance) ->', titresActivitesRelanceToSend.map(ta => ta.id).join(', ')) diff --git a/packages/api/src/tools/api-mailjet/emails.ts b/packages/api/src/tools/api-mailjet/emails.ts index 3e3525e20..482f67f01 100644 --- a/packages/api/src/tools/api-mailjet/emails.ts +++ b/packages/api/src/tools/api-mailjet/emails.ts @@ -4,6 +4,10 @@ import { EmailTemplateId } from './types' import { emailCheck } from '../email-check' import { config } from '../../config/index' import { isNotNullNorUndefined, OmitDistributive, onlyUnique } from 'camino-common/src/typescript-tools' +import { Chunk, Effect, Stream } from 'effect' +import { callAndExit } from '../fp-tools' + +const NOMBRE_DE_MAILS_EN_ENVOI_GROUPE = 2 export const mailjetSend = async (emails: readonly string[], message: OmitDistributive<CaminoMailMessage, 'To' | 'Bcc'>): Promise<void> => { try { @@ -21,15 +25,36 @@ export const mailjetSend = async (emails: readonly string[], message: OmitDistri // si on est pas sur le serveur de prod // l'adresse email du destinataire est remplacée if (config().NODE_ENV !== 'production' || config().ENV !== 'prod') { - emails = [config().ADMIN_EMAIL] + emails = [config().ADMIN_EMAIL, 'michael.bitard@beta.gouv.fr', 'bitard.michael@gmail.com'] } - const sendTo: MailjetPostMessageRecipient[] = emails.map(Email => ({ Email, Name: Email })) - const fullMessage: CaminoMailMessage = { - ...message, - ...(sendTo.length > 1 ? { Bcc: sendTo } : { To: sendTo }), + const sendTo: MailjetPostMessageRecipient[] = emails.map(Email => ({ Email })) + + if (sendTo.length === 1) { + const fullMessage: CaminoMailMessage = { + ...message, + To: sendTo, + } + await mailJetSendMail(fullMessage) + } else { + await callAndExit( + Stream.runCollect( + Stream.fromIterable(sendTo).pipe( + Stream.grouped(NOMBRE_DE_MAILS_EN_ENVOI_GROUPE), + Stream.flatMap(emailsChunk => { + const emails = Chunk.toArray(emailsChunk) + return Effect.tryPromise(async () => { + const fullMessage: CaminoMailMessage = { + ...message, + ...(emails.length === 1 ? { To: emails } : { To: [{ Email: config().ADMIN_EMAIL }], Bcc: emails }), + } + await mailJetSendMail(fullMessage) + }) + }) + ) + ) + ) } - await mailJetSendMail(fullMessage) } catch (e: any) { console.error('erreur: emailsSend', e) throw new Error(e) diff --git a/packages/api/src/tools/api-mailjet/index.ts b/packages/api/src/tools/api-mailjet/index.ts index 9d355910c..1bfb47427 100644 --- a/packages/api/src/tools/api-mailjet/index.ts +++ b/packages/api/src/tools/api-mailjet/index.ts @@ -5,7 +5,6 @@ const basicCreds = Buffer.from(`${config().API_MAILJET_KEY}:${config().API_MAILJ export interface MailjetPostMessageRecipient { Email: string - Name: string } type MailjetPostRecipients = | { @@ -18,7 +17,7 @@ type MailjetPostRecipients = } type MailjetPostMessageCommon = { From: MailjetPostMessageRecipient - ReplyTo: Omit<MailjetPostMessageRecipient, 'Name'> + ReplyTo: MailjetPostMessageRecipient } & MailjetPostRecipients interface MailjetPostBodyMessage { @@ -55,10 +54,9 @@ interface MailjetSendMailResponse { const From: MailjetPostMessageRecipient = { Email: config().API_MAILJET_EMAIL, - Name: 'Camino - le cadastre minier', } -const ReplyTo: Omit<MailjetPostMessageRecipient, 'Name'> = { +const ReplyTo: MailjetPostMessageRecipient = { Email: config().API_MAILJET_REPLY_TO_EMAIL, } -- GitLab From 9ed8a2c9fc4fe01cea35cf39a7cc8916bd23fe7c Mon Sep 17 00:00:00 2001 From: Anis Safine Laget <anis.safine@beta.gouv.fr> Date: Mon, 24 Mar 2025 17:01:32 +0100 Subject: [PATCH 3/4] add unit tests for mailjetSend --- .../titres-activites-relance-send.test.ts | 37 +- .../__snapshots__/emails.test.ts.snap | 361 ++++++++++++++++++ .../api/src/tools/api-mailjet/emails.test.ts | 30 ++ packages/api/src/tools/api-mailjet/emails.ts | 70 ++-- packages/api/tests/vitestSetup.ts | 5 - 5 files changed, 451 insertions(+), 52 deletions(-) create mode 100644 packages/api/src/tools/api-mailjet/__snapshots__/emails.test.ts.snap create mode 100644 packages/api/src/tools/api-mailjet/emails.test.ts diff --git a/packages/api/src/business/processes/titres-activites-relance-send.test.ts b/packages/api/src/business/processes/titres-activites-relance-send.test.ts index 9277fdbb9..f50a41179 100644 --- a/packages/api/src/business/processes/titres-activites-relance-send.test.ts +++ b/packages/api/src/business/processes/titres-activites-relance-send.test.ts @@ -5,6 +5,7 @@ import { vi, describe, expect, test, afterEach } from 'vitest' import { getCurrent, toCaminoDate } from 'camino-common/src/date' import { entrepriseIdValidator } from 'camino-common/src/entreprise' import { activiteIdValidator } from 'camino-common/src/activite' +import { GetEntrepriseUtilisateurs } from '../../api/rest/entreprises.queries' vi.mock('../../tools/api-mailjet/emails', () => ({ __esModule: true, @@ -23,27 +24,45 @@ describe('relance les opérateurs des activités qui vont se fermer automatiquem const date = toCaminoDate('2022-01-01') const email = 'toto.huhu@foo.com' + const secondEmail = 'test@example.org' + const troisiemeEmail = 'test2@example.org' + const fakeEmailsByEntreprise1: GetEntrepriseUtilisateurs[] = [ + { email, role: 'entreprise' }, + { email: 'emailDeBureauDEtudes', role: "bureau d'études" }, + { email: secondEmail, role: 'entreprise' }, + ] + const fakeEmailsByEntreprise2: GetEntrepriseUtilisateurs[] = [{ email: troisiemeEmail, role: 'entreprise' }] + const entrepriseId1 = entrepriseIdValidator.parse('titulaire1') const titresActivites = await checkDateAndSendEmail( - () => - Promise.resolve([ - { email, role: 'entreprise' }, - { email: 'emailDeBureauDEtudes', role: "bureau d'études" }, - ]), + entrepriseId => { + if (entrepriseId === entrepriseId1) { + return Promise.resolve(fakeEmailsByEntreprise1) + } + + return Promise.resolve(fakeEmailsByEntreprise2) + }, toCaminoDate('2022-03-18'), [ { date, id: activiteIdValidator.parse('activiteId'), titre: { - titulaireIds: [entrepriseIdValidator.parse('titulaire1')], + titulaireIds: [entrepriseIdValidator.parse('titulaire2')], + }, + }, + { + date, + id: activiteIdValidator.parse('activiteId'), + titre: { + titulaireIds: [entrepriseId1], }, }, ] ) - - expect(emailsWithTemplateSendMock).toBeCalledWith([email], EmailTemplateId.ACTIVITES_RELANCE, expect.any(Object)) - expect(titresActivites.length).toEqual(1) + expect(emailsWithTemplateSendMock).toHaveBeenCalledOnce() + expect(emailsWithTemplateSendMock).toBeCalledWith([troisiemeEmail, email, secondEmail], EmailTemplateId.ACTIVITES_RELANCE, expect.any(Object)) + expect(titresActivites.length).toEqual(2) }) test('n’envoie pas d’email aux opérateurs', async () => { diff --git a/packages/api/src/tools/api-mailjet/__snapshots__/emails.test.ts.snap b/packages/api/src/tools/api-mailjet/__snapshots__/emails.test.ts.snap new file mode 100644 index 000000000..c3993892b --- /dev/null +++ b/packages/api/src/tools/api-mailjet/__snapshots__/emails.test.ts.snap @@ -0,0 +1,361 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`mailjetSend > group by 1`] = ` +[ + [ + { + "Bcc": [ + { + "Email": "toto0@example.org", + }, + { + "Email": "toto1@example.org", + }, + { + "Email": "toto2@example.org", + }, + { + "Email": "toto3@example.org", + }, + { + "Email": "toto4@example.org", + }, + { + "Email": "toto5@example.org", + }, + { + "Email": "toto6@example.org", + }, + { + "Email": "toto7@example.org", + }, + { + "Email": "toto8@example.org", + }, + { + "Email": "toto9@example.org", + }, + { + "Email": "toto10@example.org", + }, + { + "Email": "toto11@example.org", + }, + { + "Email": "toto12@example.org", + }, + { + "Email": "toto13@example.org", + }, + { + "Email": "toto14@example.org", + }, + { + "Email": "toto15@example.org", + }, + { + "Email": "toto16@example.org", + }, + { + "Email": "toto17@example.org", + }, + { + "Email": "toto18@example.org", + }, + { + "Email": "toto19@example.org", + }, + { + "Email": "toto20@example.org", + }, + { + "Email": "toto21@example.org", + }, + { + "Email": "toto22@example.org", + }, + { + "Email": "toto23@example.org", + }, + { + "Email": "toto24@example.org", + }, + { + "Email": "toto25@example.org", + }, + { + "Email": "toto26@example.org", + }, + { + "Email": "toto27@example.org", + }, + { + "Email": "toto28@example.org", + }, + { + "Email": "toto29@example.org", + }, + { + "Email": "toto30@example.org", + }, + { + "Email": "toto31@example.org", + }, + { + "Email": "toto32@example.org", + }, + { + "Email": "toto33@example.org", + }, + { + "Email": "toto34@example.org", + }, + { + "Email": "toto35@example.org", + }, + { + "Email": "toto36@example.org", + }, + { + "Email": "toto37@example.org", + }, + { + "Email": "toto38@example.org", + }, + { + "Email": "toto39@example.org", + }, + { + "Email": "toto40@example.org", + }, + { + "Email": "toto41@example.org", + }, + { + "Email": "toto42@example.org", + }, + { + "Email": "toto43@example.org", + }, + { + "Email": "toto44@example.org", + }, + { + "Email": "toto45@example.org", + }, + { + "Email": "toto46@example.org", + }, + { + "Email": "toto47@example.org", + }, + { + "Email": "toto48@example.org", + }, + ], + "Subject": "Subject", + "TextPart": "This is a message", + "To": [ + { + "Email": "plop@plop.plop", + }, + ], + }, + ], + [ + { + "Bcc": [ + { + "Email": "toto49@example.org", + }, + { + "Email": "toto50@example.org", + }, + { + "Email": "toto51@example.org", + }, + { + "Email": "toto52@example.org", + }, + { + "Email": "toto53@example.org", + }, + { + "Email": "toto54@example.org", + }, + { + "Email": "toto55@example.org", + }, + { + "Email": "toto56@example.org", + }, + { + "Email": "toto57@example.org", + }, + { + "Email": "toto58@example.org", + }, + { + "Email": "toto59@example.org", + }, + { + "Email": "toto60@example.org", + }, + { + "Email": "toto61@example.org", + }, + { + "Email": "toto62@example.org", + }, + { + "Email": "toto63@example.org", + }, + { + "Email": "toto64@example.org", + }, + { + "Email": "toto65@example.org", + }, + { + "Email": "toto66@example.org", + }, + { + "Email": "toto67@example.org", + }, + { + "Email": "toto68@example.org", + }, + { + "Email": "toto69@example.org", + }, + { + "Email": "toto70@example.org", + }, + { + "Email": "toto71@example.org", + }, + { + "Email": "toto72@example.org", + }, + { + "Email": "toto73@example.org", + }, + { + "Email": "toto74@example.org", + }, + { + "Email": "toto75@example.org", + }, + { + "Email": "toto76@example.org", + }, + { + "Email": "toto77@example.org", + }, + { + "Email": "toto78@example.org", + }, + { + "Email": "toto79@example.org", + }, + { + "Email": "toto80@example.org", + }, + { + "Email": "toto81@example.org", + }, + { + "Email": "toto82@example.org", + }, + { + "Email": "toto83@example.org", + }, + { + "Email": "toto84@example.org", + }, + { + "Email": "toto85@example.org", + }, + { + "Email": "toto86@example.org", + }, + { + "Email": "toto87@example.org", + }, + { + "Email": "toto88@example.org", + }, + { + "Email": "toto89@example.org", + }, + { + "Email": "toto90@example.org", + }, + { + "Email": "toto91@example.org", + }, + { + "Email": "toto92@example.org", + }, + { + "Email": "toto93@example.org", + }, + { + "Email": "toto94@example.org", + }, + { + "Email": "toto95@example.org", + }, + { + "Email": "toto96@example.org", + }, + { + "Email": "toto97@example.org", + }, + ], + "Subject": "Subject", + "TextPart": "This is a message", + "To": [ + { + "Email": "plop@plop.plop", + }, + ], + }, + ], + [ + { + "Bcc": [ + { + "Email": "toto98@example.org", + }, + { + "Email": "toto99@example.org", + }, + ], + "Subject": "Subject", + "TextPart": "This is a message", + "To": [ + { + "Email": "plop@plop.plop", + }, + ], + }, + ], +] +`; + +exports[`mailjetSend > no group by 1`] = ` +[ + [ + { + "Subject": "Subject", + "TextPart": "This is a message", + "To": [ + { + "Email": "toto@example.org", + }, + ], + }, + ], +] +`; diff --git a/packages/api/src/tools/api-mailjet/emails.test.ts b/packages/api/src/tools/api-mailjet/emails.test.ts new file mode 100644 index 000000000..593a5c043 --- /dev/null +++ b/packages/api/src/tools/api-mailjet/emails.test.ts @@ -0,0 +1,30 @@ +import { test, vi, expect, describe, afterEach } from 'vitest' +import { mailjetSendForTestsOnly } from './emails' + +import { mailJetSendMail } from './index' +const mailJetSendMailMock = vi.mocked(mailJetSendMail, true) + +vi.mock('./index', () => ({ + __esModule: true, + mailjetAddContactsToGuyaneList: vi.fn().mockImplementation(a => a), + mailJetSendMail: vi.fn().mockImplementation(a => a), +})) + +describe('mailjetSend', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + test('group by', async () => { + await mailjetSendForTestsOnly( + [...Array(100).keys()].map(i => `toto${i}@example.org`), + { Subject: 'Subject', TextPart: 'This is a message' } + ) + expect(mailJetSendMailMock).toHaveBeenCalledTimes(3) + expect(mailJetSendMailMock.mock.calls).toMatchSnapshot() + }) + test('no group by', async () => { + await mailjetSendForTestsOnly(['toto@example.org'], { Subject: 'Subject', TextPart: 'This is a message' }) + expect(mailJetSendMailMock).toHaveBeenCalledOnce() + expect(mailJetSendMailMock.mock.calls).toMatchSnapshot() + }) +}) diff --git a/packages/api/src/tools/api-mailjet/emails.ts b/packages/api/src/tools/api-mailjet/emails.ts index 482f67f01..92fcaa9bd 100644 --- a/packages/api/src/tools/api-mailjet/emails.ts +++ b/packages/api/src/tools/api-mailjet/emails.ts @@ -7,54 +7,48 @@ import { isNotNullNorUndefined, OmitDistributive, onlyUnique } from 'camino-comm import { Chunk, Effect, Stream } from 'effect' import { callAndExit } from '../fp-tools' -const NOMBRE_DE_MAILS_EN_ENVOI_GROUPE = 2 +const NOMBRE_DE_MAILS_EN_ENVOI_GROUPE = 49 export const mailjetSend = async (emails: readonly string[], message: OmitDistributive<CaminoMailMessage, 'To' | 'Bcc'>): Promise<void> => { - try { - if (!Array.isArray(emails)) { - throw new Error(`un tableau d'emails est attendu ${emails}`) - } - - emails.forEach(to => { - if (!emailCheck(to)) { - throw new Error(`adresse email invalide ${to}`) - } - }) + if (!Array.isArray(emails)) { + throw new Error(`un tableau d'emails est attendu ${emails}`) + } - emails = emails.filter(isNotNullNorUndefined).filter(onlyUnique) - // si on est pas sur le serveur de prod - // l'adresse email du destinataire est remplacée - if (config().NODE_ENV !== 'production' || config().ENV !== 'prod') { - emails = [config().ADMIN_EMAIL, 'michael.bitard@beta.gouv.fr', 'bitard.michael@gmail.com'] + emails.forEach(to => { + if (!emailCheck(to)) { + throw new Error(`adresse email invalide ${to}`) } + }) + // si on est pas sur le serveur de prod + // l'adresse email du destinataire est remplacée + if (config().NODE_ENV !== 'production' || config().ENV !== 'prod') { + await mailjetSendForTestsOnly([config().ADMIN_EMAIL], message) + } else { + await mailjetSendForTestsOnly(emails.filter(isNotNullNorUndefined).filter(onlyUnique), message) + } +} +export const mailjetSendForTestsOnly = async (emails: readonly string[], message: OmitDistributive<CaminoMailMessage, 'To' | 'Bcc'>): Promise<void> => { + try { const sendTo: MailjetPostMessageRecipient[] = emails.map(Email => ({ Email })) - if (sendTo.length === 1) { - const fullMessage: CaminoMailMessage = { - ...message, - To: sendTo, - } - await mailJetSendMail(fullMessage) - } else { - await callAndExit( - Stream.runCollect( - Stream.fromIterable(sendTo).pipe( - Stream.grouped(NOMBRE_DE_MAILS_EN_ENVOI_GROUPE), - Stream.flatMap(emailsChunk => { - const emails = Chunk.toArray(emailsChunk) - return Effect.tryPromise(async () => { - const fullMessage: CaminoMailMessage = { - ...message, - ...(emails.length === 1 ? { To: emails } : { To: [{ Email: config().ADMIN_EMAIL }], Bcc: emails }), - } - await mailJetSendMail(fullMessage) - }) + await callAndExit( + Stream.runCollect( + Stream.fromIterable(sendTo).pipe( + Stream.grouped(NOMBRE_DE_MAILS_EN_ENVOI_GROUPE), + Stream.flatMap(emailsChunk => { + const emails = Chunk.toArray(emailsChunk) + return Effect.tryPromise(async () => { + const fullMessage: CaminoMailMessage = { + ...message, + ...(emails.length === 1 ? { To: emails } : { To: [{ Email: config().ADMIN_EMAIL }], Bcc: emails }), + } + await mailJetSendMail(fullMessage) }) - ) + }) ) ) - } + ) } catch (e: any) { console.error('erreur: emailsSend', e) throw new Error(e) diff --git a/packages/api/tests/vitestSetup.ts b/packages/api/tests/vitestSetup.ts index 536cfaa03..0ef18b4a9 100644 --- a/packages/api/tests/vitestSetup.ts +++ b/packages/api/tests/vitestSetup.ts @@ -1,10 +1,5 @@ import { Request, Response } from 'express' import { vi } from 'vitest' -vi.mock('../src/tools/api-mailjet/emails', () => ({ - __esModule: true, - emailsSend: vi.fn().mockImplementation(a => a), - emailsWithTemplateSend: vi.fn().mockImplementation(a => a), -})) vi.mock('../src/tools/api-mailjet/index', () => ({ __esModule: true, -- GitLab From 376297858b9f7f141a7aa3dbbcbad2f7e1646c68 Mon Sep 17 00:00:00 2001 From: Anis Safine Laget <anis.safine@beta.gouv.fr> Date: Mon, 24 Mar 2025 17:13:36 +0100 Subject: [PATCH 4/4] stream.runDrain --- packages/api/src/tools/api-mailjet/emails.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/tools/api-mailjet/emails.ts b/packages/api/src/tools/api-mailjet/emails.ts index 92fcaa9bd..0c9464976 100644 --- a/packages/api/src/tools/api-mailjet/emails.ts +++ b/packages/api/src/tools/api-mailjet/emails.ts @@ -33,7 +33,7 @@ export const mailjetSendForTestsOnly = async (emails: readonly string[], message const sendTo: MailjetPostMessageRecipient[] = emails.map(Email => ({ Email })) await callAndExit( - Stream.runCollect( + Stream.runDrain( Stream.fromIterable(sendTo).pipe( Stream.grouped(NOMBRE_DE_MAILS_EN_ENVOI_GROUPE), Stream.flatMap(emailsChunk => { -- GitLab