← Rétro-ingénierie BizTalk

STM.ICUCourriel.EAI — Spécification de l'état actuel (BizTalk)


Objectif

L'application BizTalk STM.ICUCourriel.EAI fournit une couche d'intégration pour l'envoi de courriels et le suivi des rejets (EAI = Enterprise Application Integration) pour la STM (Société de transport de Montréal). Ses responsabilités sont :

  1. Envoi de courriels — Accepter les demandes d'envoi de courriels entrantes via un point de terminaison REST/HTTP, acheminer chaque message vers l'un des deux backends de livraison (Exchange Web Services / SMTP, ou SendGrid), et persister le résultat de la livraison dans une base de données SQL de suivi.
  2. Réconciliation des rejets/suppressions — Sur un déclencheur planifié, interroger la base de données SQL de suivi pour les courriels envoyés par chaque canal et réconcilier les échecs de livraison (rebonds, blocages, rapports de spam, adresses invalides, NDR Exchange) dans cette base de données.

L'application est composée de deux assemblages BizTalk déployés comme une seule application BizTalk :

Assemblage Responsabilité
STM.ICUCourriel.EAI.EnvoiCourriel Envoi et suivi des courriels
STM.ICUCourriel.EAI.TraiteRejet Réconciliation des rejets/suppressions

Systèmes externes

Système Rôle Transport dans BizTalk
Applications appelantes (internes) Publication des demandes d'envoi de courriels POST HTTP vers l'emplacement de réception WCF-WebHttp /icucourriel/api.svc/courriel
Exchange Online (EWS / SMTP) Envoie les courriels pour les sources mappées au code de service SMTP Adaptateur BizTalk Static-DotNetSMTP, hôte ${SMTPHostName}:${SMTPPort}
SendGrid Envoie les courriels pour toutes les autres sources ; fournit aussi les API de liste de suppression (rebonds, blocages, invalides, spam) Port d'envoi WCF-WebHttp STM.ICUCourriel.EAI_SendGrid, URL de base ${UrlSendGrid}
Microsoft Entra ID (Azure AD) Émet des jetons OAuth 2.0 pour l'interrogation de suppression Exchange Online Port d'envoi WCF-WebHttp STM.ICUCourriel.EAI_OAuthToken.WCF-WebHttp, point de terminaison https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
Base de données SQL ICU Stocke et récupère les enregistrements de traitement de courriels ; interrogée via l'adaptateur WCF-SQL Ports d'envoi STM.ICUCourriel.EAI_TraitementCourriel.WCF-SQL (écriture) et STM.ICUCourriel.EAI_TraitementCourrielResponse.WCF-SQL (lecture/mise à jour)
Système de fichiers (dossier de dépôt déclencheur) Fichiers XML déclencheurs qui initient le lot de traitement de rejets Emplacement de réception FILE ${RepertoireDeclenchement}\*.xml (interrogé toutes les 60 s)
Système de fichiers (dossier de résultats) Destination des messages de sortie ResultatExecution Port d'envoi FILE STM.ICUCourriel.EAI_ResultatExecution, chemin ${ResultatExecutionRepertoire}\%MessageID%.xml

Points d'intégration / Endpoints

Emplacements de réception

Nom Protocole Adresse Pipeline Notes
STM.ICUCourriel.EAI_EnvoiCourriel.WCF-WebHttp HTTP/HTTPS /icucourriel/api.svc — opération EnvoyerCourriel POST /courriel Rcv_Json (désassembleur JSON personnalisé) Auth Basic sur TLS ; AuthorizationManager personnalisé ; max 200 appels simultanés ; max message 206 536 octets
STM.ICUCourriel.EAI_Declencheur.FILE FILE ${RepertoireDeclenchement}\*.xml PassThruReceive Interroge toutes les 60 s, taille de lot 20
STM.ICUCourriel.EAI_PurgerTraitmentCourriel.Schedule Adaptateur Schedule Quotidien à 03:00 Rcv_Nullify Exécute PurgerTraitementCourriel 100 via ScheduledTaskAdapter ; nettoie la table de suivi

Ports d'envoi

Nom Protocole Adresse / Point de terminaison Notes
STM.ICUCourriel.EAI_SMTP Static-DotNetSMTP (unidirectionnel) ${SMTPHostName}:${SMTPPort} Pipeline Snd_GenCourrielSMTP ; réessai 3×5 s ; achemine les messages en échec
STM.ICUCourriel.EAI_SendGrid WCF-WebHttp (bidirectionnel) ${UrlSendGrid} Opérations : POST /mail/send, GET /suppression/blocks, GET /suppression/bounces, GET /suppression/invalid_emails, GET /suppression/spam_reports ; en-tête Auth Authorization: Bearer ; TLS personnalisé + WCFHttpHeaderBehavior ; réessai 3×5 s
STM.ICUCourriel.EAI_OAuthToken.WCF-WebHttp WCF-WebHttp (bidirectionnel) https://login.microsoftonline.com — POST /{tenant-id}/oauth2/v2.0/token Content-Type application/x-www-form-urlencoded ; pipeline personnalisé Snd_wwwFormUrlEncoded ; pipeline de réponse Rcv_JsonToken avec désassembleur JSON personnalisé (espace de noms https://www.stm.info/ICUCourriel/MSGraph/Email/Response/1) ; aucun réessai
STM.ICUCourriel.EAI_TraitementCourriel.WCF-SQL WCF-SQL (unidirectionnel, livraison ordonnée) ${IcuDbAdress} Actions : SauvegarderTraitementCourriel, MettreAJourTraitementCourriel ; délai d'ouverture/envoi 10 min
STM.ICUCourriel.EAI_TraitementCourrielResponse.WCF-SQL WCF-SQL (bidirectionnel, intention ReadOnly) ${IcuDbAdress} Actions : MettreAJourCourrielStatus, selEAICourriel, updEAICourriel ; délai d'ouverture/envoi 10 min ; isolation Serializable
STM.ICUCourriel.EAI_ResultatExecution FILE (unidirectionnel) ${ResultatExecutionRepertoire}\%MessageID%.xml Pipeline Snd_Nullify ; filtre sur BTS.MessageType = ResultatExecution ET Medium = Courriel

Flux de messages

1. EnvoyerCourriel (Coordinateur d'envoi de courriels)

Orchestration : STM.ICUCourriel.EAI.EnvoiCourriel.EnvoyerCourriel

Liaison de port BizTalk : MessageBox (liaison directe) — s'active sur tout message de type STM.ICUSchema.EAI.Schema.MessageUsager.Courriel arrivant du port de réception STM.ICUCourriel.EAI_EnvoiCourriel.

Déclencheur

POST HTTP entrant vers /icucourriel/api.svc/courriel portant une charge utile JSON désérialisée en MessageUsagerCourriel (STM.ICUSchema.EAI.Schema.MessageUsager.Courriel).

Étapes

  1. Journalisation du début de traitement (DEBUT_TRAITEMENT_INFO).
  2. Extraction de Source depuis la propriété de contexte du message STM.ICUSchema.EAI.Schema.Source.
  3. Decide_FiltreValidation — appel de EnvoyerCourrielHelper.VerifierFiltreDestinataire(MessageUsagerCourriel) :
  1. Déterminer le service de livraison : Service = EnvoyerCourrielHelper.ObtenirServiceCourriel(Source) — retourne "SMTP" ou une autre valeur (p. ex. "SENDGRID").
  2. Déterminer le nombre de lots : NbEnvoi = EnvoyerCourrielHelper.ObtenirNombreEnvoiParCourriel(MessageUsagerCourriel, Service).
  3. Boucle IdxEnvoi de 1 à NbEnvoi :
  1. Journalisation d'avertissement : "Impossible d'envoyer un courriel (Filtre)".
  2. Génération d'un nouveau GUID comme ExtMessageId.
  3. Affectation Statut = "Filtre", Service = "Aucun".
  4. Appel SauvegarderTraitementCourriel(MessageUsagerCourriel, Service, ExtMessageId, Statut) pour persister l'enregistrement filtré.
  5. Construction ResultatExecution = EnvoyerCourrielHelper.CreerResultatExecution(MessageUsagerCourriel, Statut).
  6. Envoi ResultatExecution vers ResultatExecutionFiltrePort (liaison directe MessageBox → acheminé vers le port d'envoi FILE par filtre d'abonnement sur le type de message + medium = Courriel).
  7. Journalisation de la fin de traitement (FIN_TRAITEMENT_INFO).

Points de décision / Gestion des erreurs

Schémas de données référencés


2. EnvoyerEwsCourriel (Livraison Exchange / SMTP)

Orchestration : STM.ICUCourriel.EAI.EnvoiCourriel.EnvoyerEwsCourriel

Invocable : oui (appelée de façon synchrone depuis EnvoyerCourriel quand Service == "SMTP")

Paramètres en entrée : courriel (MessageUsager.Courriel) ; en sortie : Statut (string), MessageId (string)

Déclencheur

Appelée de façon synchrone depuis EnvoyerCourriel.

Étapes

  1. Affectation Service = "SMTP".
  2. Entrée dans une portée atomique synchronisée Scope_EnvoyerEwsCourriel :
  1. Gestionnaire d'exception Catch_SMTPException (System.Exception) :

Points de décision / Gestion des erreurs

Schémas de données référencés


3. EnvoyerSendGridCourriel (Livraison SendGrid)

Orchestration : STM.ICUCourriel.EAI.EnvoiCourriel.EnvoyerSendGridCourriel

Invocable : oui (appelée de façon synchrone depuis EnvoyerCourriel quand Service != "SMTP")

Paramètres en entrée : courriel (MessageUsager.Courriel), Source (string) ; en sortie : MessageId (string), Statut (string)

Déclencheur

Appelée de façon synchrone depuis EnvoyerCourriel.

Étapes

  1. Affectation Service = "SENDGRID".
  2. Entrée dans une portée atomique (non synchronisée) Scope_SendGridCourriel :
  1. Gestionnaire d'exception CatchException_SendGrid (System.Exception) :

Points de décision / Gestion des erreurs

Schémas de données référencés


4. SauvegarderTraitementCourriel (Persistance SQL — sous-orchestration partagée)

Orchestration : STM.ICUCourriel.EAI.EnvoiCourriel.SauvegarderTraitementCourriel

Appelée par : EnvoyerEwsCourriel, EnvoyerSendGridCourriel et EnvoyerCourriel (chemin filtre)

Cette orchestration est référencée dans tout le code, mais son fichier source .odx n'était pas inclus dans l'ensemble analysé. Son contrat est connu depuis le schéma et les sites d'appel :


5. TraiterRejets (Coordinateur de réconciliation des rejets)

Orchestration : STM.ICUCourriel.EAI.TraiteRejet.TraiterRejets

Liaison de port BizTalk : Réception depuis STM.ICUCourriel.EAI_Declencheur (adaptateur FILE).

Déclencheur

Arrivée de tout fichier *.xml dans ${RepertoireDeclenchement} (interrogé toutes les 60 secondes). Le contenu du fichier n'est pas consommé sémantiquement — il sert purement de déclencheur planificateur.

Étapes

  1. Réception du message déclencheur msgDeclencheur (type System.Xml.XmlDocument).
  2. Journalisation du début de traitement.
  3. Construction de msgSelEAICourriel :
  1. Envoi de msgSelEAICourriel vers selEAICourrielPort → STM.ICUCourriel.EAI_TraitementCourrielResponse.WCF-SQL, action TypedProcedure/dbo/selEAICourriel.
  2. Réception de msgSelEAICourrielResponse — le jeu de résultats SQL des courriels en attente de réconciliation de rejets, filtré par cdInterface = "traiterRejets".
  3. Exécution parallèle de 5 sous-orchestrations (toutes reçoivent msgSelEAICourrielResponse) :
  1. Construction de msgUpdEAICourriel :
  1. Envoi de msgUpdEAICourriel vers updEAICourrielPort → STM.ICUCourriel.EAI_TraitementCourrielResponse.WCF-SQL, action TypedProcedure/dbo/updEAICourriel.
  2. Réception de msgUpdEAICourrielResponse.
  3. Journalisation de la fin de traitement.

Points de décision / Gestion des erreurs

Schémas de données référencés


6. ObtenirTokenOAuth (Acquisition de jeton OAuth 2.0)

Orchestration : STM.ICUCourriel.EAI.TraiteRejet.ObtenirTokenOAuth

Invocable : oui (appelée depuis les sous-orchestrations de rejet qui interrogent Exchange Online)

Paramètres en sortie : token (string)

Déclencheur

Appelée de façon synchrone depuis les sous-orchestrations de réconciliation de rejets (spécifiquement MettreAJourRejetServeurExchange).

Étapes

  1. Entrée dans une portée atomique Scope_OAuthToken :
  1. Gestionnaire d'exception Catch_DeliveryFailureException (Microsoft.XLANGs.BaseTypes.DeliveryFailureException) :
  1. Gestionnaire d'exception Catch_Exception (System.Exception) :

Points de décision / Gestion des erreurs

Schémas de données référencés


Problèmes connus et risques

1. Complexité du double backend de livraison sans stratégie de sélection claire

ObtenirServiceCourriel(Source) détermine si un message va vers EWS/SMTP ou SendGrid en fonction du champ Source. Cette logique de mappage réside entièrement dans une classe helper compilée (EnvoyerCourrielHelper) — elle n'est pas visible dans les schémas ou les orchestrations, créant une règle de routage opaque qui ne peut être modifiée sans redéployer l'assemblage.

2. La gestion de la clé API SendGrid est opaque

ObtenirSendGridAPIKey() est un appel de méthode helper sans surface de configuration visible. La clé est probablement stockée dans une application affiliée SSO BizTalk, un fichier de configuration ou une base de données. Il s'agit d'une lacune d'audit de sécurité.

3. Le jeton OAuth retourné comme chaîne vide en cas d'échec — pas de disjoncteur

Quand ObtenirTokenOAuth échoue, il retourne token = "". Il n'y a pas de garde dans les orchestrations appelantes pour sauter l'étape d'interrogation Exchange si le jeton est vide. Cela cause probablement une erreur HTTP 401 en aval contre l'API Exchange, résultant en une instance d'orchestration suspendue.

4. Aucune logique de réessai dans les orchestrations

Ni EnvoyerEwsCourriel ni EnvoyerSendGridCourriel ne réessaient en cas d'échec au niveau de l'orchestration. Le seul réessai disponible est le nombre de réessais du port d'envoi BizTalk (3 réessais, intervalle de 5 secondes pour SendGrid ; 3 réessais pour SMTP). Les instances en échec enregistrent simplement Statut = "Echec" sans escalade.

5. TraiterRejets n'a pas d'isolation d'erreur entre les branches parallèles

Les cinq branches parallèles dans TraiterRejets (Exchange, Bounces, Blocks, Invalids, Spam) ne partagent aucun try/catch. Un échec dans n'importe quelle branche fera suspendre l'instance complète de l'orchestration TraiterRejets, bloquant la mise à jour de l'horodatage updEAICourriel pour tous les canaux.

6. Le déclencheur basé sur fichier pour TraiterRejets est fragile

Le lot de rejets est déclenché par le dépôt de fichier dans ${RepertoireDeclenchement}. Il n'y a aucune garantie intégrée d'exécution unique, aucune lettre morte pour le fichier déclencheur, et aucun historique des fichiers déclencheurs.

7. Faute de frappe dans le code de production : ObtenriGuid() (et non ObtenirGuid())

Le nom de méthode ObtenriGuid (manque le second 'i' dans Obtenir) apparaît dans deux orchestrations (EnvoyerCourriel et EnvoyerEwsCourriel). Il s'agit d'une faute de frappe dans la classe helper compilée qui a été perpétuée en production.

8. ResultatExecution est écrit dans un répertoire du système de fichiers

Le message de résultat d'exécution est acheminé vers un port d'envoi FILE. Il s'agit d'un patron résiduel qui ne fournit aucune sémantique de file d'attente durable et dépend de la disponibilité du système de fichiers partagé.

9. La portée EWS est synchronisée (verrou exclusif), pas atomique avec rollback

Scope_EnvoyerEwsCourriel est synchronized, ce qui signifie qu'il utilise la portée synchronisée à longue durée de BizTalk. Cela ne fournit pas de rollback ACID ; cela ne fait que sérialiser l'accès aux objets partagés en mémoire. Dans un scénario multi-instance, cela ajoute de la contention sans fournir de sécurité transactionnelle.

10. La taille maximale de message sur le point de terminaison de réception est basse (206 536 octets)

L'emplacement de réception HTTP plafonne les messages à environ 202 Ko. Les courriels avec de grosses pièces jointes seraient rejetés au niveau de la couche de réception.


Dépendances

Dépendances d'exécution

Composant Type Objectif
Assemblage STM.ICUCourriel.EAI.Helper Helper BizTalk personnalisé Contient EnvoyerCourrielHelper, EnvoyerSendGridCourrielHelper, TraiterRejetsHelper, AuthorizationManager personnalisé, Logger
Assemblage STM.ICUSchema.EAI Assemblage de schéma partagé Définit MessageUsager.Courriel, ResultatExecution, propriétés de contexte Source, IDMessage, Medium
Assemblage STM.WCFHttpHeaderBehavior.EAI Comportement de point de terminaison WCF Injecte la clé API SendGrid comme en-tête HTTP Authorization au moment de l'envoi
Assemblage STM.TLSEndpointBehavior.EAI Comportement de point de terminaison WCF Configure le TLS mutuel sur les connexions WCF-WebHttp sortantes (SendGrid, OAuth)
Assemblage STM.JsonAssembler.EAI Composant de pipeline personnalisé Désassembleur JSON utilisé dans les pipelines Rcv_Json et Rcv_JsonToken
ScheduledTaskAdapter Adaptateur BizTalk Pilote la purge quotidienne de la table TraitementCourriel
Base de données SQL ICU (${IcuDbAdress} / ${IcuDbAdoAdress}) SQL Server Stocke les enregistrements de traitement de courriels ; expose les procédures stockées : SauvegarderTraitementCourriel, MettreAJourTraitementCourriel, MettreAJourCourrielStatus, selEAICourriel, updEAICourriel, PurgerTraitementCourriel
Exchange Online (EWS) SaaS externe Livre les courriels pour les sources routées SMTP
SendGrid SaaS externe Livre les courriels pour les sources non SMTP ; fournit les API de suppression
Microsoft Entra ID SaaS externe Émet des jetons OAuth 2.0 pour les appels à l'API Exchange Online

Variables de configuration (paramétrées au déploiement)

Variable Usage
${BtsProcessingHostInstance} Hôte d'orchestration
${BtsSendHostInstance} Hôte d'adaptateur d'envoi
${BtsIsolatedHostInstanceName} Hôte de réception isolé (HTTP)
${BtsReceiveHostInstanceNameNonHa} Hôte de réception non HA (adaptateur schedule)
${BtsHostInstanceNTGroupName} Groupe NT pour les hôtes approuvés
${SMTPHostName}, ${SMTPPort} Relais Exchange/SMTP
${UrlSendGrid} URL de base de l'API SendGrid
${IcuDbAdress} Chaîne de connexion WCF-SQL
${IcuDbAdoAdress} Chaîne de connexion ADO.NET (purge par adaptateur schedule)
${ResultatExecutionRepertoire} Répertoire de sortie pour les résultats d'exécution
${RepertoireDeclenchement} Répertoire de dépôt pour les fichiers déclencheurs de lot de rejets

Diagramme d'architecture

Le diagramme montre la topologie BizTalk complète pour STM.ICUCourriel.EAI. Les deux assemblages BizTalk (EnvoiCourriel et TraiteRejet) sont présentés comme des grappes d'orchestrations séparées.

graph LR
    subgraph InternalCallers["Appelants internes"]
        IC[Clients HTTP internes]
        FS_IN["Déclencheur système de fichiers\n${RepertoireDeclenchement}/*.xml"]
        SCHED["Adaptateur Schedule\npurge quotidienne 03:00"]
    end

    subgraph ReceiveLocations["Emplacements de réception"]
        RL1["EAI_EnvoiCourriel.WCF-WebHttp\n/icucourriel/api.svc POST /courriel\nAuth Basic · Rcv_Json · max 202 Ko"]
        RL2["EAI_Declencheur.FILE\n${RepertoireDeclenchement}/*.xml\ninterrogation 60s · lot 20"]
        RL3["EAI_PurgerTraitmentCourriel.Schedule\nAdaptateur Schedule · 03:00 quotidien"]
    end

    subgraph MessageBox["BizTalk MessageBox (pub/sub)"]
        MB[(MessageBox\nSQL Server)]
    end

    subgraph EnvoiCourrielAssembly["Assemblage : STM.ICUCourriel.EAI.EnvoiCourriel"]
        O1["EnvoyerCourriel\nFiltre → routage par Source\n→ boucle de lot"]
        O2["EnvoyerEwsCourriel\nLivraison SMTP\n(portée synchronisée)"]
        O3["EnvoyerSendGridCourriel\nLivraison SendGrid"]
        O4["SauvegarderTraitementCourriel\nHelper de persistance SQL"]
    end

    subgraph TraiteRejetAssembly["Assemblage : STM.ICUCourriel.EAI.TraiteRejet"]
        O5["TraiterRejets\nCoordinateur\n(fan-out parallèle × 5)"]
        O6["ObtenirTokenOAuth\nAcquisition de jeton Entra ID"]
        O7["MettreAJourRejetServeurExchange\nRéconciliation NDR Exchange"]
        O8["MettreAJourBouncesSendGrid"]
        O9["MettreAJourBlocksSendGrid"]
        O10["MettreAJourInvalidsSendGrid"]
        O11["MettreAJourSpamsSendGrid"]
    end

    subgraph SendPorts["Ports d'envoi"]
        SP1["EAI_SMTP\nStatic-DotNetSMTP\n${SMTPHostName}:${SMTPPort}"]
        SP2["EAI_SendGrid.WCF-WebHttp\n${UrlSendGrid}\nPOST /mail/send\nGET /suppression/*"]
        SP3["EAI_OAuthToken.WCF-WebHttp\nlogin.microsoftonline.com\nPOST /oauth2/v2.0/token"]
        SP4["EAI_TraitementCourriel.WCF-SQL\nSauvegarder / MettreAJour\n(unidirectionnel, ordonné)"]
        SP5["EAI_TraitementCourrielResponse.WCF-SQL\nMettreAJourStatus / selEAICourriel\nupdEAICourriel (bidirectionnel)"]
        SP6["EAI_ResultatExecution\nFILE · ${ResultatExecutionRepertoire}"]
    end

    subgraph ExternalSystems["Systèmes externes"]
        SMTP_SVC[Exchange Online\nEWS / SMTP]
        SG[SendGrid\nSaaS courriel]
        ENTRA[Microsoft Entra ID\noauth2/v2.0/token]
        SQL[(Base de données SQL ICU\n${IcuDbAdress})]
        FS_OUT[Système de fichiers\n${ResultatExecutionRepertoire}]
    end

    IC -->|POST /courriel JSON| RL1
    FS_IN -->|déclencheur *.xml| RL2
    SCHED -->|minuterie| RL3

    RL1 --> MB
    RL2 --> MB
    RL3 --> MB

    MB -->|MessageUsagerCourriel| O1
    MB -->|declencheur| O5

    O1 -->|Service=SMTP| O2
    O1 -->|Service!=SMTP| O3
    O1 -->|chemin Filtre| O4
    O2 -->|appel| O4
    O3 -->|appel| O4

    O5 -->|parallèle| O7
    O5 -->|parallèle| O8
    O5 -->|parallèle| O9
    O5 -->|parallèle| O10
    O5 -->|parallèle| O11
    O7 -->|appel| O6

    O2 --> SP1
    O3 --> SP2
    O6 --> SP3
    O8 --> SP2
    O9 --> SP2
    O10 --> SP2
    O11 --> SP2
    O4 --> SP4
    O5 --> SP5
    O1 --> SP6

    SP1 -->|SMTP/TLS| SMTP_SVC
    SP2 -->|HTTPS| SG
    SP3 -->|HTTPS| ENTRA
    SP4 -->|WCF-SQL| SQL
    SP5 -->|WCF-SQL| SQL
    SP6 --> FS_OUT
    RL3 -->|purge ADO| SQL

    style O6 fill:#f0ad4e,color:#000
    style SP6 fill:#ccc,color:#000

Diagramme de séquence

Cette séquence couvre le flux principal d'envoi de courriels (le chemin le plus complexe — envoi SendGrid multi-destinataires), suivi du déclenchement de la réconciliation des rejets.

sequenceDiagram
    autonumber
    actor Caller as Appelant interne
    participant RL as Emplacement de réception BizTalk<br/>(EAI_EnvoiCourriel)
    participant MB as BizTalk MessageBox
    participant EnvC as EnvoyerCourriel<br/>orchestration
    participant EnvSG as EnvoyerSendGridCourriel<br/>sous-orchestration
    participant EnvEWS as EnvoyerEwsCourriel<br/>sous-orchestration
    participant SaveDB as SauvegarderTraitementCourriel<br/>sous-orchestration
    participant SG as API SendGrid
    participant SMTP as Exchange / SMTP
    participant SQL as Base de données SQL ICU
    participant FS as Répertoire de résultats

    Caller->>RL: POST /icucourriel/api.svc/courriel (JSON, auth Basic)
    RL->>MB: Publication MessageUsagerCourriel (désérialisé)
    MB->>EnvC: Activation (liaison directe sur MessageUsager.Courriel)
    EnvC->>EnvC: ObtenirServiceCourriel(Source)\n→ "SMTP" ou "SENDGRID"
    EnvC->>EnvC: ObtenirNombreEnvoiParCourriel(message, Service)

    alt Destinataire filtré
        EnvC->>SaveDB: appel(Service=Aucun, Statut=Filtre)
        SaveDB->>SQL: exec SauvegarderTraitementCourriel
        EnvC->>MB: Publication ResultatExecution(Filtre)
        MB->>FS: Le port d'envoi FILE écrit le XML
    else Routage SMTP (par sous-message dans la boucle)
        loop IdxEnvoi 1..NbEnvoi
            EnvC->>EnvC: ObtenirSousCourriel(message, idx, SMTP)
            EnvC->>EnvEWS: appel(MessageSousCourriel)
            EnvEWS->>SMTP: Envoi via l'adaptateur Static-DotNetSMTP
            alt Succès
                EnvEWS->>SaveDB: appel(Statut=Envoyé, MessageId=nouveauGUID)
                SaveDB->>SQL: exec SauvegarderTraitementCourriel
            else System.Exception
                EnvEWS->>SaveDB: appel(Statut=Echec, MessageId=courriel.IDMessage)
                SaveDB->>SQL: exec SauvegarderTraitementCourriel
            end
        end
        EnvC->>MB: Publication ResultatExecution
        MB->>FS: Le port d'envoi FILE écrit le XML
    else Routage SendGrid (par sous-message dans la boucle)
        loop IdxEnvoi 1..NbEnvoi
            EnvC->>EnvC: ObtenirSousCourriel(message, idx, SENDGRID)
            EnvC->>EnvSG: appel(MessageSousCourriel, Source)
            EnvSG->>EnvSG: ObtenirSendGridAPIKey() → en-tête
            EnvSG->>SG: POST /mail/send {personalizations, from, subject, content}
            SG-->>EnvSG: 202 Accepted, en-tête X-Message-Id
            alt Succès
                EnvSG->>SaveDB: appel(Statut=Envoyé, MessageId=X-Message-Id)
                SaveDB->>SQL: exec SauvegarderTraitementCourriel
            else System.Exception
                EnvSG->>SaveDB: appel(Statut=Echec, MessageId=courriel.IDMessage)
                SaveDB->>SQL: exec SauvegarderTraitementCourriel
            end
        end
        EnvC->>MB: Publication ResultatExecution
        MB->>FS: Le port d'envoi FILE écrit le XML
    end

    Note over MB,SQL: La réconciliation des rejets s'exécute sur un déclencheur fichier séparé
    participant FT as Dépôt fichier déclencheur
    participant TR as TraiterRejets<br/>orchestration
    participant OA as ObtenirTokenOAuth<br/>sous-orchestration
    FT->>MB: Dépôt de fichier dans ${RepertoireDeclenchement}
    MB->>TR: Activation
    TR->>SQL: exec selEAICourriel(cdInterface=traiterRejets)
    SQL-->>TR: enregistrements de courriels en attente

    par 5 branches parallèles
        TR->>OA: appel (pour la branche Exchange)
        OA->>ENTRA as Entra ID: POST /oauth2/v2.0/token
        ENTRA-->>OA: access_token
        OA-->>TR: token
        TR->>SMTP: Requête NDR (API Exchange Graph)
    and
        TR->>SG: GET /suppression/bounces
    and
        TR->>SG: GET /suppression/blocks
    and
        TR->>SG: GET /suppression/invalid_emails
    and
        TR->>SG: GET /suppression/spam_reports
    end

    TR->>SQL: exec updEAICourriel(cdInterface, dtDerniereExecution)

Estimation de l'effort de migration

Orchestration Complexité Effort (jours) Bloquant Équivalent Azure
EnvoyerCourriel Moyenne 3 Nécessite la décompilation de EnvoyerCourrielHelper — la table de routage ObtenirServiceCourriel(Source) est opaque ; impossible d'écrire la logique de routage sans elle Workflow Logic Apps Standard avec état
EnvoyerEwsCourriel Moyenne 2 Confirmer si l'adaptateur SMTP Exchange Online est encore utilisé ; si oui, porter vers l'API Graph sendMail — nécessite un enregistrement d'application Entra avec Mail.Send Action HTTP Logic Apps → API Graph
EnvoyerSendGridCourriel Basse 2 Récupérer la clé API SendGrid du SSO BizTalk avant la migration ; confirmer la construction du corps ObtenirSendGridMessage Action HTTP Logic Apps → API SendGrid v3
SauvegarderTraitementCourriel Basse 1 La signature de la procédure SQL est entièrement visible ; aucun bloquant Appel SQL en ligne dans Logic App via connecteur Azure SQL
TraiterRejets Haute 4 Cinq branches parallèles ; l'isolation des erreurs doit être repensée — le code actuel n'a aucune gestion d'exception par branche. Nécessite une décision des opérations STM sur le suivi indépendant des horodatages Azure Durable Function fan-out/fan-in
ObtenirTokenOAuth Basse 0,5 Peut être entièrement remplacé par l'identité managée — aucune migration de code nécessaire si la permission Graph Mail.Send est accordée L'identité managée remplace l'acquisition manuelle de jeton
MettreAJourRejetServeurExchange Moyenne 2 Le patron de requête NDR Exchange doit être documenté (filtre de requête API Graph, boîte aux lettres à interroger) — non visible dans le code source BizTalk Activity Durable Function
MettreAJourBouncesSendGrid / MettreAJourBlocksSendGrid / MettreAJourInvalidsSendGrid / MettreAJourSpamsSendGrid Basse (chacune) 1 par Les points de terminaison et la pagination de l'API de suppression SendGrid sont bien documentés ; aucune décompilation d'assemblage nécessaire Activities Durable Function (4 × 1 jour)
Composants de pipeline JSON personnalisés (Rcv_Json, Rcv_JsonToken, Snd_wwwFormUrlEncoded) Basse 1 Décompiler pour vérifier l'absence de transformation non standard ; remplacer par System.Text.Json Analyse JSON native Logic Apps
AuthorizationManager (auth HTTP Basic) Basse 1 Nécessite l'extraction de la liste d'appelants autorisés depuis l'assemblage compilé pour configurer correctement la politique JWT APIM Politique validate-jwt APIM (Entra ID)
Inventaire des variables de déploiement Moyenne 1 Sept variables de configuration ne sont pas dans le code source — récupérer des scripts de déploiement Azure App Settings / références Key Vault

Effort total estimé : 22 jours-développeur (développeur unique ; ObtenirTokenOAuth peut être parallélisé avec TraiterRejets une fois l'échafaudage Durable Function en place).


Liste de vérification pour le développeur

Compléter ces étapes avant d'écrire tout code Azure pour cette intégration.

1. Récupération du code source

2. Extraction des identifiants et de la configuration

3. Base de données

4. Exchange Online

5. Vérification opérationnelle

6. Environnement de développement local