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