Accueil › Vue d'ensemble
45
Orchestrations totales
10 solutions BizTalk
6
Canaux de communication
SMS, Courriel, Vocal, Social, Massif, Interne
12
Complexité élevée
27 % de toutes les orchestrations
4
Phases
+ 4 candidats au retrait
7
Systèmes externes
Telus, VuDu, SendGrid, X, GLID, Exchange, AzureAD
5
Patrons d'authentification
Basic, Bearer, OAuth2, Personnalisé, Intégré
Carte des connexions système
Top 3 des intégrations critiques
| 1. ICU Mediator | Point d'entrée de la plateforme; tous les canaux en dépendent. 5 orchestrations, livraison ordonnée, patrons de corrélation. |
| 2. Pipeline Courriel | 14 orchestrations couvrant SendGrid, Exchange, OAuth2, SMTP, traitement des rebonds. Plus grand nombre de solutions. |
| 3. Vocal / Telus | Orchestration en convoi, gestion des tentatives, vérification d'erreurs contre Telus RVI. Plus haute complexité par orchestration. |
Top 3 des risques de migration
| 1. Patron Convoi | EnvoyerTelusAppelVocal utilise le convoi BizTalk — aucun équivalent direct dans Azure. Nécessite Durable Functions ou sessions Service Bus. |
| 2. Livraison ordonnée | EnvoyerMessageUsager du Médiateur repose sur la livraison ordonnée de BizTalk. Doit utiliser des sessions Service Bus ou des Durable Functions mono-instance. |
| 3. Diversité d'auth. | 5 patrons d'authentification distincts à travers 7 systèmes externes. Chacun nécessite une gestion de justificatifs native Azure (Key Vault, Managed Identity). |
Répartition par canal
14
Courriel
9
SMS
8
Vocal
7
Interne
5
Social
2
Massif
Accueil › Inventaire
Inventaire des intégrations
45 orchestrations
| Solution | Orchestration | Canal | Dir. | Transport | Auth. | Complexité | Phase |
|---|
Accueil › Canal SMS
Canal SMS — STM.ICUTexto.EAI
8
Orchestrations
VuDu
Fournisseur SMS
Personnalisé
Patron d'auth.
Phase 1
Phase de migration
Le pipeline SMS reçoit les messages du ICU Mediator, les envoie via l'API VuDu (unitaire + lot), enregistre la livraison en SQL et interroge les mises à jour de statut de livraison.
Architecture — État actuel
Détails des orchestrations
| Orchestration | Direction | Système externe | Transport | Auth. | Complexité | Notes |
|---|---|---|---|---|---|---|
EnvoyerMessageUsagerTexto | Entrant | ICU Mediator | WCF-WebHttp | Basic sur TLS | Moyenne | Point d'entrée SMS |
EnvoyerSMS | Bidirectionnel | VuDu API, BD SQL | WCF-WebHttp + WCF-SQL | Personnalisé + Intégré | Moyenne | Répartition principale |
EnvoyerSMSVudu | Sortant | VuDu SMS API | WCF-WebHttp | En-tête personnalisé | Moyenne | VuDu POST /send |
EnvoyerSMSVuduBatch | Sortant | VuDu SMS API | WCF-WebHttp | En-tête personnalisé | Moyenne | VuDu POST /send_batch |
SauvegarderDetailsEnvoi | Sortant | BD SQL | WCF-SQL | Intégré | Faible | Suivi SQL |
MettreAJourBroadcast | Bidirectionnel | VuDu API + BD SQL | WCF-WebHttp + WCF-SQL | Personnalisé + Intégré | Moyenne | Interrogation du statut de livraison VuDu |
MettreAJourFeed | Bidirectionnel | VuDu API + BD SQL | WCF-WebHttp + WCF-SQL | Personnalisé + Intégré | Faible | DÉSENRÔLÉ — code mort |
Note :
MettreAJourFeed est DÉSENRÔLÉ (code mort). Confirmer avec la STM avant le retrait.Architecture cible Azure
Correspondance de migration
| Composant BizTalk | Remplacement Azure | Justification |
|---|---|---|
| Réception WCF-WebHttp (Basic) | Azure APIM + OAuth2 | Authentification centralisée, limitation de débit, surveillance |
| Orchestration EnvoyerSMS | Azure Function (déclencheur Service Bus) | Répartition sans état; pas de convoi nécessaire |
| Auth. en-tête personnalisé VuDu | Secret Key Vault + HttpClient | Rotation gérée des justificatifs |
| Suivi WCF-SQL | Azure SQL + EF Core / Dapper | Même schéma, BD gérée |
| Interrogation MettreAJourBroadcast | Fonction déclenchée par minuterie | Remplace la planification BizTalk |
| MettreAJourFeed | ÉLIMINÉ | Code mort — désenrôlé dans BizTalk |
Gain clé : Le pipeline SMS est le canal le plus simple. Aucun patron de convoi, aucune livraison ordonnée. Candidat idéal pour la Phase 1 afin de bâtir la confiance de l'équipe avant de s'attaquer au Courriel (Phase 2).
Accueil › Canal Courriel
Canal Courriel — STM.ICUCourriel.EAI
14
Orchestrations
3
Fournisseurs externes
SendGrid, Exchange, SMTP
4
Patrons d'auth.
Basic, Bearer, OAuth2, Client Creds
Phase 2
Phase de migration
Alerte de complexité : Le pipeline Courriel a le plus grand nombre d'orchestrations (14) et la surface d'authentification la plus diversifiée. Inclut une orchestration manuelle de jeton OAuth2 (
ObtenirTokenOAuth) qui est éliminée dans Azure via Managed Identity.Architecture — État actuel
Détails des orchestrations
| Orchestration | Direction | Système externe | Auth. | Complexité | Notes |
|---|---|---|---|---|---|
ProduireMessageUsagerCourriel | Entrant | ICU Mediator | Basic sur TLS | Élevée | Point d'entrée |
EnvoyerCourriel | Bidirectionnel | BD SQL + routage | Aucune | Élevée | Route SMTP vs SendGrid |
EnvoyerEwsCourriel | Sortant | Relais SMTP | Aucune | Moyenne | SMTP hérité |
EnvoyerSendGridCourriel | Sortant | SendGrid API | Bearer | Moyenne | SendGrid /mail/send |
SauvegarderTraitementCourriel | Sortant | BD SQL | Intégré | Faible | Écriture de suivi |
TraiterRejets | Entrant | BD SQL (déclencheur FICHIER) | Aucune + Intégré | Élevée | Boucle de traitement des rebonds |
MettreAJourRejetServeurExchange | Bidirectionnel | Exchange Online | OAuth2 | Élevée | Lecteur NDR Exchange |
MettreAJourBouncesSendGrid | Bidirectionnel | SendGrid API | Bearer | Moyenne | Interrogation des rebonds |
MettreAJourBlocksSendGrid | Bidirectionnel | SendGrid API | Bearer | Moyenne | Interrogation des blocages |
MettreAJourSpamsSendGrid | Bidirectionnel | SendGrid API | Bearer | Moyenne | Interrogation des pourriels |
MettreAJourInvalidsSendGrid | Bidirectionnel | SendGrid API | Bearer | Moyenne | Interrogation des invalides |
ObtenirTokenOAuth | Sortant | Azure AD | Client Credentials | Moyenne | OAuth2 manuel — ÉLIMINÉ dans Azure |
MettreAJourStatus | Sortant | BD SQL | Intégré | Faible | Mise à jour du statut |
Architecture cible Azure
Correspondance de migration
| Composant BizTalk | Remplacement Azure | Notes |
|---|---|---|
| ProduireMessageUsagerCourriel | APIM + Service Bus + Function | Découpler le point d'entrée du traitement |
| Routeur EnvoyerCourriel | Logique de routage dans Function | Piloté par config : SendGrid vs Graph API |
| EnvoyerEwsCourriel (SMTP) | Microsoft Graph API | Remplacement moderne du relais SMTP |
| EnvoyerSendGridCourriel | SDK SendGrid dans Function | Appel SDK direct, sans surcharge WCF |
| 4x interrogateurs SendGrid | Fonction Timer unique | Consolidation rebonds/blocages/pourriels/invalides en une seule |
| ObtenirTokenOAuth | ÉLIMINÉ (Managed Identity) | Simplification majeure — plus d'orchestration manuelle de jetons |
| MettreAJourRejetServeurExchange | Abonnements Graph API | Mode push au lieu de l'interrogation pour la lecture des NDR |
| TraiterRejets (déclencheur FICHIER) | Abonnement au topic Service Bus | Événementiel au lieu du dépôt de fichier |
Gains clés : ObtenirTokenOAuth complètement éliminé. Quatre interrogateurs SendGrid consolidés en une seule fonction. Relais SMTP remplacé par MS Graph. Déclencheurs par fichier remplacés par Service Bus événementiel.
Accueil › Architecture Azure
Architecture cible Azure
Architecture de haut niveau
Correspondance BizTalk vers Azure
| Concept BizTalk | Équivalent Azure | Service | Notes |
|---|---|---|---|
| MessageBox | Topics/Files Service Bus | Azure Service Bus | Pub/sub + sémantique de file |
| Orchestration | Azure Function / Durable Function | Azure Functions | Sans état si possible; Durable pour le convoi |
| Adaptateur WCF-WebHttp | HttpClient / SDK | Azure Functions | Appels HTTP directs avec politiques de réessai |
| Adaptateur WCF-SQL | EF Core / Dapper | Azure Functions | Auth. Managed Identity vers Azure SQL |
| Adaptateur FILE | Déclencheur Blob Storage | Azure Blob Storage | Ou éliminé si les dépôts de fichiers sont inutiles |
| Routage basé sur le contenu | Filtres de topic Service Bus | Azure Service Bus | Expressions de filtre SQL sur les propriétés de message |
| Patron Convoi | Durable Functions (sessions) | Azure Functions | Requis pour le canal Vocal |
| Livraison ordonnée | Sessions Service Bus | Azure Service Bus | Garantie FIFO basée sur les sessions |
| Console Admin BizTalk | Portail Azure / App Insights | Azure Monitor | Tableaux de bord, alertes, analytique de journaux |
| Magasin de justificatifs SSO | Azure Key Vault | Azure Key Vault | Secrets centralisés avec rotation |
Arbre de décision : quand utiliser quoi
Utiliser Azure Functions (sans état) quand :
- Patron requête-réponse simple
- Aucune corrélation entre les messages
- Aucune gestion d'état de réessai nécessaire
- Exemples : envoi SMS, envoi SendGrid, écritures SQL
Utiliser Durable Functions quand :
- Patrons convoi / fan-out-fan-in
- Exécution longue avec état de réessai
- Corrélation entre les messages
- Exemples : pipeline Vocal/Telus, corrélation Médiateur
Utiliser Service Bus quand :
- Découplage des producteurs et consommateurs
- Livraison ordonnée (sessions)
- Gestion des lettres mortes
- Exemples : files de canaux, topics de rebonds
Utiliser Logic Apps quand :
- Scénarios B2B / EDI complexes (non applicable ici)
- Flux de travail visuel requis pour les développeurs citoyens
- L'avantage des connecteurs intégrés justifie le coût
- Actuellement : non recommandé pour ICU EAI
Accueil › Code Azure
Code Azure — Fichiers de déploiement
Ébauches de déploiement Azure — ÉCHAFAUDAGE, RÉVISION PAR UN ARCHITECTE REQUISE.
Seuls SMS et Courriel sont spécifiés; les autres solutions (Médiateur, Médias sociaux, Voix)
seront générées lors des phases subséquentes.
▶ apim-sms-api.yaml
openapi: 3.0.3
info:
title: STM ICUTexto SMS API
description: |
Azure API Management facade for the STM ICUTexto SMS integration.
Replaces BizTalk WCF-WebHttp receive locations:
- STM.ICUTexto.EAI_EnvoiTexto (/icutexto/api.svc, POST /texto)
- STM.ICUTexto.EAI_ReceptionVudu (/icutexto/vudu/api.svc, POST /inbound)
All inbound calls are authenticated via OAuth 2.0 bearer tokens validated
by APIM against Azure AD (Entra ID), replacing the custom
STM.ICUTexto.EAI.Helper.Securite.AuthorizationManager WCF behavior.
version: "1.0.0"
contact:
name: STM Integration Team
servers:
- url: https://{apimName}.azure-api.net/icutexto
description: Azure API Management — STM ICUTexto namespace
variables:
apimName:
default: stm-apim
description: APIM instance name — set at deployment time per STM naming standards
security:
- BearerAuth: []
tags:
- name: sms-outbound
description: |
Outbound SMS operations. Replaces the EnvoyerSMS and EnvoyerSMSVudu
BizTalk orchestrations and the STM.ICUTexto.EAI_EnvoiTexto receive location.
- name: sms-status
description: |
Delivery status operations. Replaces the DemanderStatus and MettreAJourStatus
BizTalk orchestrations and the WCF-SQL TypedPolling receive locations.
- name: sms-webhook
description: |
Vudu-to-STM inbound webhook. Replaces the STM.ICUTexto.EAI_ReceptionVudu
receive location and the vuduInbound2MessageUsagerEntrant BizTalk map.
paths:
# ---------------------------------------------------------------------------
# POST /sms/send
# Replaces: BizTalk receive location STM.ICUTexto.EAI_EnvoiTexto
# POST /texto (single-recipient path through EnvoyerSMS orchestration)
# APIM routes to backend: sms-outbound-function (Azure Function fn-envoyer-sms)
# The function validates, filters, then hands off to fn-envoyer-sms-vudu via
# Service Bus sms-outbound topic.
# ---------------------------------------------------------------------------
/sms/send:
post:
operationId: sendSingleSms
summary: Send a single SMS message
description: |
Accepts a validated SMS send request for a single recipient.
APIM injects X-Correlation-Id, validates JWT, enforces rate limit (200/s),
then forwards to the sms-outbound Azure Function backend.
Returns 202 Accepted — processing is asynchronous. Use GET /sms/status/{messageId}
to poll delivery status (replaces BizTalk synchronous call pattern).
BizTalk equivalent: POST /texto → EnvoyerSMS orchestration (single-recipient path).
tags:
- sms-outbound
x-azure-apim-policy:
inbound: |
<policies>
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized: valid bearer token required">
<openid-config url="https://login.microsoftonline.com/{tenantId}/.well-known/openid-configuration"/>
<audiences>
<audience>api://stm-icutexto</audience>
</audiences>
<issuers>
<issuer>https://sts.windows.net/{tenantId}/</issuer>
</issuers>
</validate-jwt>
<rate-limit calls="200" renewal-period="1"/>
<set-header name="X-Correlation-Id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<choose>
<when condition="@(context.Request.Headers.GetValueOrDefault("Content-Type","").Contains("application/json") == false)">
<return-response>
<set-status code="415" reason="Unsupported Media Type"/>
<set-body>{"error":"Content-Type must be application/json"}</set-body>
</return-response>
</when>
</choose>
<log-to-eventhub logger-id="appinsights-logger">@(string.Format("sms.send.attempt|correlationId={0}|path={1}", context.RequestId, context.Request.Url.Path))</log-to-eventhub>
<set-backend-service backend-id="sms-outbound-function"/>
</inbound>
<backend>
<forward-request timeout="30"/>
</backend>
<outbound>
<set-header name="X-Correlation-Id" exists-action="override">
<value>@(context.RequestId)</value>
</set-header>
</outbound>
<on-error>
<log-to-eventhub logger-id="appinsights-logger">@(string.Format("sms.error|correlationId={0}|statusCode={1}|message={2}", context.RequestId, context.Response.StatusCode, context.LastError.Message))</log-to-eventhub>
</on-error>
</policies>
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SmsSendRequest'
example:
destinataires: "+15141234567"
contenu: "Votre autobus arrive dans 3 minutes."
source: "ICU"
responses:
'202':
description: |
Accepted. Message queued for asynchronous processing.
Use Location header to poll delivery status.
headers:
Location:
description: URL to poll for delivery status
schema:
type: string
format: uri
example: /icutexto/sms/status/MSG-2026-001234
X-Correlation-Id:
description: Request correlation identifier propagated through the pipeline
schema:
type: string
format: uuid
content:
application/json:
schema:
$ref: '#/components/schemas/SmsSendAccepted'
'400':
description: Malformed JSON or missing required fields
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Missing or invalid OAuth 2.0 bearer token
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'415':
description: Content-Type is not application/json
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: Rate limit exceeded (200 calls/second)
headers:
Retry-After:
schema:
type: integer
description: Seconds to wait before retrying
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
# ---------------------------------------------------------------------------
# POST /sms/send-batch
# Replaces: EnvoyerSMSVudu sub-orchestration (multi-recipient / batch path)
# APIM routes to: fn-envoyer-sms-vudu (Azure Durable Function, fan-out pattern)
# The Durable Function calls Vudu POST /send_batch per recipient in parallel,
# replacing the sequential while loop in the BizTalk EnvoyerSMSVudu orchestration.
# ---------------------------------------------------------------------------
/sms/send-batch:
post:
operationId: sendBatchSms
summary: Send SMS to multiple recipients (batch)
description: |
Accepts a batch SMS send request with multiple recipients.
APIM applies the same JWT validation and rate limiting as /sms/send,
then routes to the Durable Function backend for fan-out processing.
Each recipient is dispatched in parallel to Vudu POST /send_batch,
replacing the BizTalk sequential EnvoyerSMSVudu while loop.
RetryPolicy: 3 attempts, exponential backoff 5s→60s (replaces RetryCount=0).
Returns 202 Accepted immediately. Poll status per messageId.
BizTalk equivalent: POST /texto → EnvoyerSMS → EnvoyerSMSVudu (batch path).
tags:
- sms-outbound
x-azure-apim-policy:
inbound: |
<policies>
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized: valid bearer token required">
<openid-config url="https://login.microsoftonline.com/{tenantId}/.well-known/openid-configuration"/>
<audiences>
<audience>api://stm-icutexto</audience>
</audiences>
</validate-jwt>
<rate-limit calls="200" renewal-period="1"/>
<set-header name="X-Correlation-Id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<log-to-eventhub logger-id="appinsights-logger">@(string.Format("sms.send-batch.attempt|correlationId={0}|recipientCount={1}", context.RequestId, context.Request.Body.As<JObject>()["destinataires"]?.Count() ?? 0))</log-to-eventhub>
<set-backend-service backend-id="sms-batch-function"/>
</inbound>
<backend>
<forward-request timeout="30"/>
</backend>
<outbound>
<set-header name="X-Correlation-Id" exists-action="override">
<value>@(context.RequestId)</value>
</set-header>
</outbound>
</policies>
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SmsBatchSendRequest'
example:
destinataires:
- "+15141234567"
- "+15149876543"
contenu: "Alerte de service: délai sur la ligne 14."
source: "ICU"
idMessage: "BATCH-2026-001"
responses:
'202':
description: Batch accepted for asynchronous processing
headers:
Location:
description: URL to poll batch status
schema:
type: string
format: uri
X-Correlation-Id:
schema:
type: string
format: uuid
content:
application/json:
schema:
$ref: '#/components/schemas/SmsSendAccepted'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
# ---------------------------------------------------------------------------
# GET /sms/status/{messageId}
# Replaces: DemanderStatus + MettreAJourStatus orchestrations
# plus WCF-SQL TypedPolling receive location (PollTraitementSMSBroadcast)
# APIM routes to: fn-demander-status (Azure Function, queries Azure SQL + Vudu)
# The async status poll pattern replaces BizTalk's synchronous result return.
# ---------------------------------------------------------------------------
/sms/status/{messageId}:
get:
operationId: getSmsStatus
summary: Get delivery status for a sent message
description: |
Returns the current delivery status for a previously submitted SMS message.
The backend Function queries Azure SQL (dbo.PollTraitementSMSBroadcast equivalent)
and, if status is PENDING, may invoke Vudu POST /get_broadcast to refresh.
Replaces: DemanderStatus orchestration + MettreAJourStatus polling loop.
The silent exception swallow in BizTalk's catch block is replaced with
explicit error response — dead-letter on Vudu failure requires ops sign-off
(see spec Risk #4).
BizTalk equivalent: MettreAJourStatus → DemanderStatus → Vudu /get_broadcast.
tags:
- sms-status
x-azure-apim-policy:
inbound: |
<policies>
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
<openid-config url="https://login.microsoftonline.com/{tenantId}/.well-known/openid-configuration"/>
<audiences>
<audience>api://stm-icutexto</audience>
</audiences>
</validate-jwt>
<rate-limit calls="100" renewal-period="1"/>
<set-header name="X-Correlation-Id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<set-backend-service backend-id="sms-status-function"/>
</inbound>
</policies>
parameters:
- name: messageId
in: path
required: true
description: |
The unique message identifier returned in the Location header of the
/sms/send or /sms/send-batch response. Maps to IdMessage in Azure SQL
dbo.TraitementSMS. Corresponds to msg_id returned by Vudu /send.
schema:
type: string
minLength: 1
maxLength: 100
example: MSG-2026-001234
responses:
'200':
description: Status retrieved successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SmsStatusResponse'
examples:
success:
summary: Successfully delivered
value:
idMessage: "MSG-2026-001234"
statut: "SUCCES"
messageErreur: null
nbDestinataires: 1
nbLivres: 1
horodatage: "2026-03-21T14:30:00Z"
pending:
summary: Still processing
value:
idMessage: "MSG-2026-001235"
statut: "EN_COURS"
messageErreur: null
nbDestinataires: 3
nbLivres: 1
horodatage: "2026-03-21T14:31:00Z"
filtered:
summary: Filtered by destination filter
value:
idMessage: "MSG-2026-001236"
statut: "FILTRE"
messageErreur: "Recipient filtered by VerifierFiltreDestinataire"
nbDestinataires: 1
nbLivres: 0
horodatage: "2026-03-21T14:32:00Z"
'401':
$ref: '#/components/responses/Unauthorized'
'404':
description: Message ID not found in Azure SQL TraitementSMS
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
# ---------------------------------------------------------------------------
# POST /sms/status/callback
# Replaces: STM.ICUTexto.EAI_ReceptionVudu receive location
# (/icutexto/vudu/api.svc, POST /inbound)
# vuduInbound2MessageUsagerEntrant BizTalk map
# APIM validates Vudu shared secret (replaces custom AuthorizationManager for
# this endpoint), transforms payload, publishes to Service Bus sms-inbound topic.
# NOTE: Full vuduInbound.xsd schema not yet reviewed — see spec Risk #9.
# ---------------------------------------------------------------------------
/sms/status/callback:
post:
operationId: vuduDeliveryCallback
summary: Receive Vudu delivery status webhook callback
description: |
Inbound webhook called by Vudu to report delivery status of sent messages.
APIM validates the X-Vudu-Signature header (HMAC shared secret) instead of
the custom BizTalk AuthorizationManager. Payload is forwarded to
fn-reception-sms (Azure Function) which publishes to Service Bus sms-inbound.
The BizTalk map vuduInbound2MessageUsagerEntrant is replaced by inline
Function transformation logic. Full vuduInbound schema must be confirmed
with Vudu documentation before implementation (spec Risk #9).
BizTalk equivalent: STM.ICUTexto.EAI_ReceptionVudu → vuduInbound2MessageUsagerEntrant
→ MessageUsagerEntrant → sms-inbound MessageBox routing.
This endpoint does NOT require an internal OAuth 2.0 bearer token.
Authentication uses the X-Vudu-Signature HMAC header validated in APIM policy.
tags:
- sms-webhook
security:
- VuduSignature: []
x-azure-apim-policy:
inbound: |
<policies>
<inbound>
<!-- Validate Vudu HMAC shared secret — replaces BizTalk custom AuthorizationManager -->
<!-- TODO: Replace {vuduSharedSecretKeyVaultRef} with Key Vault named value reference -->
<check-header name="X-Vudu-Signature" failed-check-httpcode="401" failed-check-error-message="Invalid Vudu signature" ignore-case="false">
<value>{{vudu-webhook-secret}}</value>
</check-header>
<set-header name="X-Source" exists-action="override">
<value>vudu-inbound</value>
</set-header>
<set-header name="X-Correlation-Id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<log-to-eventhub logger-id="appinsights-logger">@(string.Format("sms.callback.received|correlationId={0}", context.RequestId))</log-to-eventhub>
<set-backend-service backend-id="sms-inbound-function"/>
</inbound>
<backend>
<forward-request timeout="15"/>
</backend>
</policies>
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/VuduCallbackPayload'
example:
msg_id: 987654
status: true
status_detail: "DELIVRD"
to: "+15141234567"
timestamp: "2026-03-21T14:35:00Z"
responses:
'200':
description: Callback accepted and queued for processing
content:
application/json:
schema:
type: object
properties:
received:
type: boolean
example: true
'400':
$ref: '#/components/responses/BadRequest'
'401':
description: Invalid or missing Vudu HMAC signature
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
$ref: '#/components/responses/InternalError'
# ==============================================================================
# Components
# ==============================================================================
components:
securitySchemes:
BearerAuth:
type: oauth2
description: |
OAuth 2.0 client credentials flow via Azure AD (Entra ID).
Replaces HTTP Basic auth + custom STM.ICUTexto.EAI.Helper.Securite.AuthorizationManager.
Internal callers must obtain tokens from:
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
using client_id / client_secret registered for the calling application.
Token audience: api://stm-icutexto
flows:
clientCredentials:
tokenUrl: https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
scopes:
api://stm-icutexto/.default: Full access to ICUTexto SMS API
VuduSignature:
type: apiKey
in: header
name: X-Vudu-Signature
description: |
HMAC shared secret provided by Vudu for webhook authentication.
Stored in Azure Key Vault as vudu-webhook-secret and referenced via
APIM named value. Replaces BizTalk custom AuthorizationManager on
the STM.ICUTexto.EAI_ReceptionVudu receive location.
schemas:
# -------------------------------------------------------------------------
# SmsSendRequest
# Derived from ICUTextoSchema.xsd (Mediator schema)
# Namespace: http://STM.ICUTexto.EAI.ICUTextoSchema
# Replaces the BizTalk MessageUsagerTexto message type
# -------------------------------------------------------------------------
SmsSendRequest:
type: object
required:
- destinataires
- contenu
properties:
destinataires:
type: string
description: |
Recipient phone number(s) in E.164 format or token type as accepted
by Vudu /send endpoint. Maps to the `to` field in Vudu API calls.
Derived from ICUTextoSchema.xsd destinataires element.
example: "+15141234567"
minLength: 1
maxLength: 500
contenu:
type: string
description: |
SMS message body text. Maps to the `message` field in Vudu API calls.
Derived from ICUTextoSchema.xsd contenu element.
example: "Votre autobus arrive dans 3 minutes."
minLength: 1
maxLength: 1600
source:
type: string
description: |
Identifies the sending system / source. Used to resolve the correct
Vudu auth token from Key Vault (keyed as vudu-auth-token-{source}).
Replaces Source property used in DemanderStatusHelper.ObtenirAuthorizationToken.
example: "ICU"
maxLength: 50
idMessage:
type: string
description: |
Optional client-supplied message identifier. If not provided, one is
generated by the Function. Propagated as X-Correlation-Id through
the pipeline. Maps to IdMessage in Azure SQL dbo.TraitementSMS.
example: "MSG-2026-001234"
maxLength: 100
# -------------------------------------------------------------------------
# SmsBatchSendRequest
# Multi-recipient variant — routes to fn-envoyer-sms-vudu Durable Function
# Replaces the batch path in EnvoyerSMSVudu orchestration
# -------------------------------------------------------------------------
SmsBatchSendRequest:
type: object
required:
- destinataires
- contenu
properties:
destinataires:
type: array
description: |
Array of recipient phone numbers in E.164 format. Each is dispatched
to Vudu POST /send_batch as a separate fan-out activity.
Derived from nbDestinataires / EnvoyerSMSVudu orchestration logic.
items:
type: string
minItems: 2
maxItems: 1000
example:
- "+15141234567"
- "+15149876543"
contenu:
type: string
description: SMS message body — same content sent to all recipients in the batch.
example: "Alerte de service: délai sur la ligne 14."
minLength: 1
maxLength: 1600
source:
type: string
description: Sending system identifier. Used to resolve Vudu credentials from Key Vault.
example: "ICU"
maxLength: 50
idMessage:
type: string
description: |
Client-supplied batch identifier. Used as the Service Bus session key for
ordered delivery to Azure SQL (replaces OrderedDelivery=true on
STM.ICUTexto.EAI_SQLTraitementSms send port — see spec Risk #5).
example: "BATCH-2026-001"
maxLength: 100
# -------------------------------------------------------------------------
# SmsSendAccepted
# Async acknowledgement — returned on 202 Accepted
# The async pattern replaces BizTalk synchronous call + ResultatExecution FILE output
# -------------------------------------------------------------------------
SmsSendAccepted:
type: object
properties:
idMessage:
type: string
description: Assigned message ID. Use this to poll /sms/status/{messageId}.
example: "MSG-2026-001234"
statut:
type: string
enum: [ACCEPTE]
description: Always ACCEPTE on 202 response. Final status available via status poll.
example: "ACCEPTE"
correlationId:
type: string
format: uuid
description: APIM-generated X-Correlation-Id propagated through the async pipeline.
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# -------------------------------------------------------------------------
# SmsStatusResponse
# Derived from ResultatExecution schema
# Namespace: http://www.stm.info/namespaces/internal/icu/resultatexecution/1
# Replaces ResultatExecution BizTalk message + FILE send port archive
# -------------------------------------------------------------------------
SmsStatusResponse:
type: object
required:
- idMessage
- statut
properties:
idMessage:
type: string
description: Message identifier. Maps to IdMessage in Azure SQL dbo.TraitementSMS.
example: "MSG-2026-001234"
statut:
type: string
description: |
Current delivery status. Maps to Statut column in dbo.TraitementSMS.
SUCCES / ERREUR / FILTRE mirror the ResultatExecution schema values.
EN_COURS is new — indicates async processing not yet complete.
enum:
- SUCCES
- ERREUR
- FILTRE
- EN_COURS
example: "SUCCES"
messageErreur:
type: string
nullable: true
description: |
Error detail when statut=ERREUR. Maps to MessageErreur in ResultatExecution.
Populated from Vudu status_detail or Function exception message.
example: null
nbDestinataires:
type: integer
description: Total number of intended recipients.
example: 1
nbLivres:
type: integer
description: Number of confirmed delivered recipients per Vudu ClxFullDeliveryReport.
example: 1
horodatage:
type: string
format: date-time
description: Timestamp of the last status update (UTC).
example: "2026-03-21T14:30:00Z"
vuduMsgId:
type: integer
nullable: true
description: |
Vudu-assigned msg_id from /send response. Used as the broadcast parameter
for /get_broadcast status polls. Stored in Azure SQL dbo.TraitementSMS.
example: 987654
correlationId:
type: string
format: uuid
nullable: true
description: X-Correlation-Id propagated from the original send request.
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# -------------------------------------------------------------------------
# VuduCallbackPayload
# Inbound webhook payload from Vudu — delivered to POST /sms/status/callback
# Derived from vuduInbound.xsd (UTF-16, not fully reviewed — spec Risk #9)
# Schema is illustrative; must be verified against live Vudu documentation.
# -------------------------------------------------------------------------
VuduCallbackPayload:
type: object
description: |
Delivery status callback from Vudu. Schema derived from analysis of
vuduInbound.xsd and VuduProperty.xsd. Must be verified against live
Vudu API documentation before implementing fn-reception-sms transformation.
Replaces: BizTalk ReceptionSMS orchestration + vuduInbound2MessageUsagerEntrant map.
properties:
msg_id:
type: integer
description: Vudu message ID. Maps to msg_id returned by /send. Used to correlate with idMessage in Azure SQL.
example: 987654
status:
type: boolean
description: Overall delivery success indicator.
example: true
status_detail:
type: string
description: |
Delivery status detail code (e.g. DELIVRD, UNDELIV, EXPIRED).
Maps to status_detail from Vudu /send response schema.
example: "DELIVRD"
to:
type: string
description: Recipient phone number this callback applies to.
example: "+15141234567"
timestamp:
type: string
format: date-time
description: Vudu-reported delivery timestamp (UTC).
example: "2026-03-21T14:35:00Z"
broadcast_id:
type: integer
nullable: true
description: |
Batch/broadcast identifier for multi-recipient sends.
Used to correlate with dbo.CreerTraitementSMSBatch records.
example: 456789
# -------------------------------------------------------------------------
# ErrorResponse
# Standard error envelope — replaces BizTalk FILE-based error archive
# (STM.ICUTexto.EAI_Erreur FILE send port)
# -------------------------------------------------------------------------
ErrorResponse:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable error message.
example: "Invalid request: contenu field is required"
correlationId:
type: string
format: uuid
nullable: true
description: X-Correlation-Id for tracing in Application Insights.
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
code:
type: string
nullable: true
description: Machine-readable error code for client error handling.
example: "VALIDATION_ERROR"
responses:
BadRequest:
description: Malformed request body or missing required fields
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Missing or invalid OAuth 2.0 bearer token
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
RateLimited:
description: Rate limit exceeded
headers:
Retry-After:
schema:
type: integer
description: Seconds to wait before retrying
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
InternalError:
description: Internal server error — check Application Insights for sms.error events
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
▶ apim-email-api.yaml
openapi: 3.0.3
info:
title: STM ICUCourriel Email API
description: >
Email dispatch and rejection-reconciliation API exposed through Azure API Management.
Replaces the BizTalk WCF-WebHttp receive location on STM.ICUCourriel.EAI and its
custom AuthorizationManager. All authentication is now delegated to Microsoft Entra ID
(OAuth 2.0 client credentials flow) validated by the APIM JWT validation policy.
version: "1.0.0"
contact:
name: STM Integration Team
servers:
- url: https://api.stm.info/icucourriel/v1
description: Production — Azure APIM gateway
# ---------------------------------------------------------------------------
# Security schemes
# ---------------------------------------------------------------------------
# Replaces: Basic auth on BizTalk WCF-WebHttp receive location + custom
# STM.ICUCourriel.EAI.Helper AuthorizationManager assembly.
# ---------------------------------------------------------------------------
security:
- EntraIdClientCredentials: []
components:
securitySchemes:
EntraIdClientCredentials:
type: oauth2
description: >
OAuth 2.0 client credentials flow against Microsoft Entra ID.
Callers obtain a token from
https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
and present it as a Bearer token. The APIM inbound policy validates
the JWT signature against the Entra ID OIDC discovery endpoint and
enforces the audience claim "api://stm-icucourriel".
flows:
clientCredentials:
tokenUrl: https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
scopes:
api://stm-icucourriel/.default: Full access to the ICUCourriel email API
# -------------------------------------------------------------------------
# Schemas
# -------------------------------------------------------------------------
schemas:
# -----------------------------------------------------------------------
# Derived from STM.ICUSchema.EAI.Schema.MessageUsager.Courriel and
# ICUCourrielSchema.xsd. Field names preserve the French abbreviations
# used in the internal canonical schema to avoid a breaking contract change
# for existing callers. A /v2 route can introduce English field names
# after caller migration.
# -----------------------------------------------------------------------
SendEmailRequest:
type: object
required:
- idMessage
- source
- a
- objet
- contenu
properties:
idMessage:
type: string
maxLength: 100
description: >
Caller-supplied unique message identifier. Mapped to the
idMessage column in SauvegarderTraitementCourriel.
example: "MSG-2026-001234"
source:
type: string
maxLength: 100
description: >
Routing discriminator used by ObtenirServiceCourriel to select
EWS (Exchange/Graph API) or SendGrid as the delivery channel.
Valid values are defined in the ICU routing configuration.
example: "ALERTE-PERTURBATION"
medium:
type: string
default: "Courriel"
description: >
Message medium. Must be "Courriel" for this API. Retained for
compatibility with the upstream MessageUsager schema which also
supports SMS and Push.
enum:
- Courriel
a:
type: string
description: >
Recipient To address(es). Multiple addresses separated by
semicolons, matching the BizTalk canonical field name "a"
(abbreviated from "à" in ICUCourrielSchema.xsd).
example: "user@example.com;other@example.com"
cc:
type: string
description: CC address(es), semicolon-separated.
example: "manager@example.com"
cci:
type: string
description: BCC address(es), semicolon-separated (maps to "cci" = copie conforme invisible).
example: "archive@stm.info"
objet:
type: string
description: Email subject line.
example: "Avis de perturbation — Ligne 80"
contenu:
type: string
description: >
Email body. HTML or plain text. Max 10 MB total payload
(configurable; replaces the 202 KB cap enforced by the BizTalk
receive location).
example: "<p>Des perturbations sont prévues...</p>"
SendEmailResponse:
type: object
description: >
Synchronous execution result. Mirrors the ResultatExecution BizTalk
message published to the STM.ICUCourriel.EAI_ResultatExecution FILE
send port, now returned as a JSON HTTP response (or delivered as a
Service Bus topic message in async mode).
properties:
idMessage:
type: string
description: Echo of the caller-supplied idMessage.
statut:
type: string
enum:
- Envoyé
- Filtre
- Echec
description: >
Delivery status. "Filtre" means the recipient matched the
VerifierFiltreDestinataire filter and no send was attempted.
serviceCourriel:
type: string
enum:
- SMTP
- SENDGRID
- Aucun
description: >
Delivery channel selected by ObtenirServiceCourriel. "Aucun"
when statut is "Filtre".
extMessageId:
type: string
description: >
External message identifier returned by the delivery backend.
For SendGrid: the X-Message-Id response header value.
For Exchange Graph API: the message ID from the sendMail response.
Empty string when statut is "Filtre" or "Echec".
AcceptedResponse:
type: object
description: >
Returned when the API is configured for async dispatch (preferred
production pattern). APIM enqueues the message to the
icucourriel-envoi Service Bus queue and returns 202 immediately.
Callers poll GET /email/status/{messageId} or subscribe to the
icucourriel-resultat Service Bus topic.
properties:
correlationId:
type: string
description: >
Correlation ID assigned by APIM (context.RequestId). Also
forwarded as the x-correlation-id header to all downstream
services (Logic Apps, Application Insights).
example: "3f7a1c22-9e4b-4d81-a703-1bc45f8e2a0d"
statusUrl:
type: string
format: uri
description: URL to poll for the execution result.
example: "https://api.stm.info/icucourriel/v1/email/status/3f7a1c22-9e4b-4d81-a703-1bc45f8e2a0d"
StatusResponse:
type: object
description: Execution result for a previously accepted async request.
properties:
correlationId:
type: string
statut:
type: string
enum:
- Pending
- Envoyé
- Filtre
- Echec
description: >
"Pending" while the Logic App workflow is in progress.
Terminal states match SendEmailResponse.statut.
serviceCourriel:
type: string
extMessageId:
type: string
completedAt:
type: string
format: date-time
nullable: true
description: ISO 8601 timestamp when the workflow completed. Null while Pending.
RejectionSyncRequest:
type: object
description: >
Optional body for POST /email/rejections/sync. All fields are
optional — omitting them triggers a full reconciliation run using the
last-processed timestamp stored in the ICU database (selEAICourriel
cdInterface='traiterRejets').
properties:
sinceUtc:
type: string
format: date-time
nullable: true
description: >
Override the last-processed timestamp. If provided, all five
rejection-reconciliation activities (Exchange NDR, SendGrid
bounces/blocks/invalids/spam) will query from this point.
Useful for replaying a failed reconciliation run.
channels:
type: array
items:
type: string
enum:
- exchange
- sendgrid-bounces
- sendgrid-blocks
- sendgrid-invalids
- sendgrid-spam
description: >
Subset of reconciliation channels to run. Omit to run all five
(equivalent to TraiterRejets running all parallel sub-orchestrations).
RejectionSyncResponse:
type: object
properties:
orchestrationId:
type: string
description: Durable Function orchestration instance ID for tracking.
statusUrl:
type: string
format: uri
description: >
Durable Function status endpoint URL for polling orchestration
progress (fan-out/fan-in across all five rejection channels).
SendGridWebhookEvent:
type: object
description: >
Single event object from a SendGrid event webhook POST. SendGrid
delivers an array of these. The APIM webhook endpoint receives the
array and forwards each event to the email-rejection-queue Service Bus
queue for processing by the TraiterRejets Durable Function.
properties:
email:
type: string
format: email
timestamp:
type: integer
description: Unix timestamp of the event.
event:
type: string
enum:
- bounce
- blocked
- invalid_email
- spamreport
- delivered
- open
- click
description: >
Only "bounce", "blocked", "invalid_email", and "spamreport" events
are actionable by the TraiterRejets replacement logic. Other event
types are logged to Application Insights but not enqueued.
sg_message_id:
type: string
description: SendGrid message ID — correlates with extMessageId stored by SauvegarderTraitementCourriel.
reason:
type: string
description: Bounce or block reason string, if present.
status:
type: string
description: SMTP status code string, if present.
type:
type: string
description: Bounce type (hard/soft), populated for bounce events.
ErrorResponse:
type: object
properties:
code:
type: string
description: Machine-readable error code.
message:
type: string
description: Human-readable description.
correlationId:
type: string
description: APIM request correlation ID for log lookup in Application Insights.
# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
paths:
# -------------------------------------------------------------------------
# POST /email/send
#
# Replaces: BizTalk WCF-WebHttp receive location on
# STM.ICUCourriel.EAI_ReceptionCourriel (port 443, URI /courriel).
#
# Backend: Azure Logic Apps Standard HTTP trigger
# (stm-icucourriel-envoicourriel / SendEmail workflow).
#
# Routing logic (ObtenirServiceCourriel) is executed inside the Logic App,
# not in APIM. APIM is a pure gateway here: auth, size guard, rate limit,
# correlation header, backend forward.
# -------------------------------------------------------------------------
/email/send:
post:
operationId: sendEmail
summary: Send an email via EWS (Exchange Graph API) or SendGrid
description: >
Accepts an email dispatch request and routes it to the appropriate
delivery backend. In synchronous mode (default for low-volume
callers) returns the ResultatExecution immediately. In async mode
returns 202 Accepted with a correlation ID; callers poll
GET /email/status/{messageId}.
**BizTalk equivalent:** POST to the WCF-WebHttp receive location
that triggered the EnvoyerCourriel orchestration.
tags:
- Email Dispatch
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SendEmailRequest"
examples:
sendgrid:
summary: SendGrid routing example
value:
idMessage: "MSG-2026-001234"
source: "ALERTE-PERTURBATION"
medium: "Courriel"
a: "subscriber@example.com"
objet: "Perturbation ligne 80"
contenu: "<p>Service perturbé de 08h00 à 10h00.</p>"
ews:
summary: Exchange (EWS/Graph) routing example
value:
idMessage: "MSG-2026-001235"
source: "INTERNAL-RELAY"
medium: "Courriel"
a: "employee@stm.info"
cc: "manager@stm.info"
objet: "Internal notification"
contenu: "Plain text body."
responses:
"200":
description: >
Email dispatched synchronously. Logic App completed the
EnvoyerCourriel → SendEmail workflow before returning.
content:
application/json:
schema:
$ref: "#/components/schemas/SendEmailResponse"
"202":
description: >
Email accepted for async dispatch. APIM enqueued the message to
the icucourriel-envoi Service Bus queue. Poll statusUrl for result.
headers:
Location:
description: Status polling URL.
schema:
type: string
format: uri
x-correlation-id:
description: APIM-assigned correlation ID, forwarded to Logic Apps and Application Insights.
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/AcceptedResponse"
"400":
description: Invalid request payload (schema validation failure).
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"401":
description: Missing or invalid Bearer token. JWT validation policy rejected the token.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"413":
description: >
Payload exceeds 10 MB size limit (APIM size-guard policy).
Replaces the 202 KB cap on the BizTalk receive location.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"429":
description: Rate limit exceeded (APIM rate-limit-by-key policy).
headers:
Retry-After:
schema:
type: integer
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Logic App or downstream service error.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
# APIM policy annotations
x-azure-apim-policy:
inbound: |
<!--
1. Validate JWT — replaces custom AuthorizationManager in
STM.ICUCourriel.EAI.Helper assembly.
-->
<validate-jwt header-name="Authorization" failed-validation-httpcode="401"
failed-validation-error-message="Unauthorized: invalid or missing token">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://stm-icucourriel</audience>
</audiences>
</validate-jwt>
<!--
2. Size guard — replaces 202 KB cap on WCF-WebHttp receive location.
New limit: 10 MB (10 485 760 bytes).
-->
<choose>
<when condition="@(context.Request.Headers.GetValueOrDefault("Content-Length","0").As<int>() > 10485760)">
<return-response>
<set-status code="413" reason="Payload Too Large" />
<set-body>{"code":"PAYLOAD_TOO_LARGE","message":"Request body exceeds 10 MB limit."}</set-body>
</return-response>
</when>
</choose>
<!--
3. Correlation header — propagated to Logic Apps and Application Insights.
Replaces the opaque BizTalk tracking GUID.
-->
<set-header name="x-correlation-id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<!--
4. Rate limiting — 200 calls/minute per subscription key.
Equivalent to the BizTalk receive location max 200 concurrent calls.
-->
<rate-limit-by-key calls="200" renewal-period="60"
counter-key="@(context.Subscription.Id)"
increment-condition="@(true)" />
<!--
5. Backend: forward to Logic Apps Standard HTTP trigger using
managed identity (no stored secret).
Set-backend-service URL is configured in the APIM backend resource.
-->
<authentication-managed-identity resource="https://management.azure.com/" />
backend: |
<!--
Route to Logic Apps Standard HTTP trigger endpoint.
The base URL is stored as a named value in APIM:
{{logicapp-sendemail-trigger-url}}
-->
<set-backend-service base-url="{{logicapp-sendemail-trigger-url}}" />
# -------------------------------------------------------------------------
# POST /email/rejections/sync
#
# Replaces: FILE drop-folder receive location (Schedule adapter trigger) that
# fired the TraiterRejets BizTalk orchestration.
#
# Backend: Azure Durable Function HTTP starter for
# stm-icucourriel-traiterejets / ProcessRejections orchestrator.
#
# Primary trigger remains a Timer (0 0 3 * * * — daily 03:00 UTC) defined
# in the Durable Function host. This endpoint allows on-demand runs and
# partial-channel replays (useful when a single SendGrid branch failed).
# -------------------------------------------------------------------------
/email/rejections/sync:
post:
operationId: triggerRejectionSync
summary: Trigger rejection reconciliation (on-demand)
description: >
Starts the TraiterRejets Durable Function orchestration on demand.
The normal production trigger is a daily Azure Timer; this endpoint
enables manual re-runs, partial-channel replays after a failure, and
integration testing.
**BizTalk equivalent:** Dropping a trigger file into
${RepertoireDeclenchement} to fire the FILE receive location on
the TraiterRejets orchestration.
tags:
- Rejection Reconciliation
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/RejectionSyncRequest"
examples:
fullRun:
summary: Full reconciliation run (all five channels)
value: {}
partialReplay:
summary: Replay only the SendGrid bounce channel from a specific point
value:
sinceUtc: "2026-03-20T03:00:00Z"
channels:
- sendgrid-bounces
responses:
"202":
description: >
Orchestration instance started. Use statusUrl to poll the
fan-out/fan-in progress across all five rejection-reconciliation
activities.
content:
application/json:
schema:
$ref: "#/components/schemas/RejectionSyncResponse"
"401":
description: Unauthorized.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"409":
description: >
An orchestration instance is already running. Returns the ID of
the active instance. Only one reconciliation run should be active
at a time to avoid duplicate processing (risk identified in spec:
parallel branches have no isolation — duplicate processing if
dtDerniereExecution not updated per channel).
content:
application/json:
schema:
$ref: "#/components/schemas/RejectionSyncResponse"
"500":
description: Failed to start orchestration.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
x-azure-apim-policy:
inbound: |
<validate-jwt header-name="Authorization" failed-validation-httpcode="401"
failed-validation-error-message="Unauthorized">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://stm-icucourriel</audience>
</audiences>
</validate-jwt>
<set-header name="x-correlation-id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<!-- Only allow callers with the "rejection-admin" role claim -->
<check-header name="Authorization" failed-check-httpcode="403"
failed-check-error-message="Forbidden: rejection-sync requires rejection-admin role"
ignore-case="true">
</check-header>
# -------------------------------------------------------------------------
# GET /email/status/{messageId}
#
# Replaces: No direct BizTalk equivalent. BizTalk callers had to query the
# ICU SQL database directly (selEAICourriel) to check status.
# This endpoint provides a first-class polling surface, enabling
# the preferred 202 Accepted + poll pattern.
#
# Backend: Logic Apps Standard or a lightweight Azure Function that reads
# from the ICU SQL database (MettreAJourCourrielStatus view /
# selEAICourriel stored procedure).
# -------------------------------------------------------------------------
/email/status/{messageId}:
get:
operationId: getEmailStatus
summary: Poll delivery status for an async send request
description: >
Returns the current execution status for an email dispatch request.
Used by callers that received a 202 Accepted from POST /email/send.
The status is read from the ICU SQL database via
selEAICourriel / MettreAJourCourrielStatus — the same database
retained unchanged from BizTalk.
tags:
- Email Dispatch
parameters:
- name: messageId
in: path
required: true
schema:
type: string
maxLength: 100
description: >
The correlationId returned in the 202 Accepted response from
POST /email/send, or the idMessage supplied in the request body.
example: "3f7a1c22-9e4b-4d81-a703-1bc45f8e2a0d"
responses:
"200":
description: Status retrieved successfully.
content:
application/json:
schema:
$ref: "#/components/schemas/StatusResponse"
examples:
pending:
summary: Still processing
value:
correlationId: "3f7a1c22-9e4b-4d81-a703-1bc45f8e2a0d"
statut: "Pending"
serviceCourriel: null
extMessageId: null
completedAt: null
sent:
summary: Delivered via SendGrid
value:
correlationId: "3f7a1c22-9e4b-4d81-a703-1bc45f8e2a0d"
statut: "Envoyé"
serviceCourriel: "SENDGRID"
extMessageId: "sg-abc123.filteredsub1"
completedAt: "2026-03-21T14:05:32Z"
"401":
description: Unauthorized.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: >
No record found for the given messageId. Either the ID is
incorrect or the workflow has not yet created its first ICU
database record.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Backend error reading from ICU database.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
x-azure-apim-policy:
inbound: |
<validate-jwt header-name="Authorization" failed-validation-httpcode="401"
failed-validation-error-message="Unauthorized">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://stm-icucourriel</audience>
</audiences>
</validate-jwt>
<!-- Cache status responses for 5 seconds to reduce ICU SQL query load -->
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false"
allow-private-response-caching="false"
must-revalidate="false" downstream-caching-type="none" />
outbound: |
<cache-store duration="5" />
# -------------------------------------------------------------------------
# POST /email/webhook/sendgrid
#
# New endpoint — no BizTalk equivalent.
#
# SendGrid can push delivery events (bounces, blocks, spam reports) in
# near-real-time via its Event Webhook. This endpoint receives those events
# and enqueues them to the email-rejection-queue Service Bus queue for
# processing by the TraiterRejets Durable Function activities.
#
# This complements (and over time may replace) the polling approach used by
# the five MettreAJour* BizTalk sub-orchestrations.
#
# Security: SendGrid signs webhook payloads with ECDSA. The APIM policy
# validates the Sendgrid-Signature and Sendgrid-Timestamp headers.
# -------------------------------------------------------------------------
/email/webhook/sendgrid:
post:
operationId: receiveSendGridWebhook
summary: Receive SendGrid delivery event webhook (bounces, blocks, spam)
description: >
Receives SendGrid Event Webhook POST requests. SendGrid delivers an
array of event objects. APIM validates the ECDSA signature, filters
for actionable event types (bounce, blocked, invalid_email,
spamreport), and forwards each to the email-rejection-queue Service
Bus queue.
Non-actionable events (delivered, open, click) are logged to
Application Insights but not enqueued.
**Authentication:** This endpoint does NOT use OAuth 2.0. SendGrid
signs the payload using its Event Webhook public key. The APIM
policy verifies the Sendgrid-Signature header.
tags:
- Webhooks
security: [] # SendGrid signature validation replaces OAuth on this endpoint
parameters:
- name: Sendgrid-Signature
in: header
required: true
schema:
type: string
description: ECDSA signature from SendGrid Event Webhook signing key.
- name: Sendgrid-Timestamp
in: header
required: true
schema:
type: string
description: Unix timestamp for replay-attack prevention (reject if > 5 minutes old).
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/SendGridWebhookEvent"
description: Array of SendGrid event objects (batch delivery).
responses:
"200":
description: >
Events accepted. APIM returns 200 to SendGrid to acknowledge
receipt. Enqueueing to Service Bus is done asynchronously in the
APIM policy send-request action.
"400":
description: Malformed payload or missing signature headers.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"401":
description: ECDSA signature validation failed (tampered payload or wrong key).
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Failed to enqueue events to Service Bus.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
x-azure-apim-policy:
inbound: |
<!--
Validate SendGrid ECDSA webhook signature.
The SendGrid public key is stored as APIM named value:
{{sendgrid-webhook-public-key}}
Replay window: 5 minutes.
-->
<set-variable name="sendgridTimestamp" value="@(context.Request.Headers.GetValueOrDefault("Sendgrid-Timestamp", ""))" />
<set-variable name="sendgridSignature" value="@(context.Request.Headers.GetValueOrDefault("Sendgrid-Signature", ""))" />
<choose>
<when condition="@(string.IsNullOrEmpty((string)context.Variables["sendgridTimestamp"]) || string.IsNullOrEmpty((string)context.Variables["sendgridSignature"]))">
<return-response>
<set-status code="401" reason="Missing SendGrid signature headers" />
</return-response>
</when>
</choose>
<!--
NOTE: Full ECDSA verification requires a custom policy expression or
an Azure Function validate-signature sidecar. The named value
{{sendgrid-webhook-public-key}} holds the key from the SendGrid
dashboard (Settings → Mail Settings → Event Webhook → Signing Key).
-->
<!-- Forward actionable events to Service Bus email-rejection-queue -->
<!-- Non-actionable events (open, click, delivered) are dropped here -->
<set-header name="x-correlation-id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
▶ bicep-sms-infrastructure.bicep
// =============================================================================
// bicep-sms-infrastructure.bicep
// STM ICUTexto EAI — Azure Infrastructure (Phase 1)
//
// Deploys the Azure resources required to replace the STM.ICUTexto.EAI and
// STM.ICUTextoExtraction.EAI BizTalk applications.
//
// What this replaces (BizTalk → Azure mapping):
// BizTalk MessageBox (SQL Server) → Azure Service Bus
// BizTalk Orchestration Engine → Azure Functions + Durable Functions
// WCF-WebHttp receive locations → Azure API Management
// BizTalk SSO credential store → Azure Key Vault
// Custom STM.Logger + Windows Event Log → Application Insights
// FILE send ports (ResultatExecution,Erreur)→ Application Insights events
//
// Scope: Resource Group deployment
// Naming: Illustrative — update to STM naming standards at implementation time.
// See spec note: "All proposed Azure resource names are illustrative;
// naming must conform to STM naming standards at implementation time."
//
// Usage:
// az deployment group create \
// --resource-group rg-stm-icutexto-prod \
// --template-file bicep-sms-infrastructure.bicep \
// --parameters @bicep-sms-infrastructure.parameters.json
// =============================================================================
targetScope = 'resourceGroup'
// -----------------------------------------------------------------------------
// Parameters
// -----------------------------------------------------------------------------
@description('Environment tag: dev | uat | prod')
@allowed(['dev', 'uat', 'prod'])
param environment string = 'dev'
@description('Azure region for all resources')
param location string = resourceGroup().location
@description('Azure AD tenant ID for APIM JWT validation policy')
param tenantId string
@description('Object ID of the managed identity that will access Key Vault (used to set access policy)')
param functionAppManagedIdentityObjectId string = ''
@description('APIM SKU: Developer (non-production) or Standard/Premium (production). Developer has no SLA.')
@allowed(['Developer', 'Standard', 'Premium'])
param apimSku string = 'Developer'
@description('APIM publisher email — required by APIM resource provider')
param apimPublisherEmail string
@description('APIM publisher name')
param apimPublisherName string = 'STM Integration'
@description('Service Bus SKU: Standard minimum; Premium if VNet injection required for SQL private endpoint')
@allowed(['Standard', 'Premium'])
param serviceBusSku string = 'Standard'
@description('Path to the OpenAPI spec file relative to this template. Used for APIM API import.')
param openApiSpecPath string = 'apim-sms-api.yaml'
@description('Azure Functions plan SKU: Y1 (Consumption) or EP1 (Premium — required for VNet)')
@allowed(['Y1', 'EP1', 'EP2', 'EP3'])
param functionsPlanSku string = 'Y1'
@description('Log retention days in Application Insights / Log Analytics workspace')
param logRetentionDays int = 90
@description('Vudu provider URI. Can be an App Setting value if not secret. Used in Function app config.')
@secure()
param vuduProviderUri string
@description('ICU database connection string. Prefer managed identity — provided here only as fallback.')
@secure()
param icuSqlConnectionString string = ''
// -----------------------------------------------------------------------------
// Variables — naming conventions (illustrative, update to STM standards)
// -----------------------------------------------------------------------------
var prefix = 'stm-icutexto-${environment}'
var apimName = '${prefix}-apim'
var serviceBusName = '${prefix}-sb'
var keyVaultName = '${prefix}-kv' // Max 24 chars — shorten as needed
var appInsightsName = '${prefix}-ai'
var logWorkspaceName = '${prefix}-law'
var functionAppName = '${prefix}-func'
var functionStorageName = replace('${prefix}funcstor', '-', '') // Storage names: no hyphens, max 24 chars
var functionPlanName = '${prefix}-plan'
// Service Bus queue / topic names — match spec exactly
var sbQueueSmsSend = 'sms-outbound'
var sbQueueSmsSendDlq = 'sms-outbound-dlq' // Built-in DLQ on main queue; this var for doc clarity
var sbQueueSmsStatus = 'sms-status-request'
var sbQueueSmsInbound = 'sms-inbound'
var sbQueueSmsExtraction = 'sms-extraction'
var sbQueueSmsDispatch = 'sms-dispatch' // Internal: fn-envoyer-sms → fn-envoyer-sms-vudu
// =============================================================================
// Log Analytics Workspace
// Required by Application Insights (workspace-based mode)
// Replaces: Windows Event Log + custom STM.Logger
// =============================================================================
resource logWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
name: logWorkspaceName
location: location
tags: {
environment: environment
application: 'stm-icutexto-eai'
migrationSource: 'biztalk'
}
properties: {
retentionInDays: logRetentionDays
sku: {
name: 'PerGB2018'
}
features: {
enableLogAccessUsingOnlyResourcePermissions: true
}
}
}
// =============================================================================
// Application Insights
// Replaces:
// - STM.ICUTexto.EAI.Helper.Logging.Logger (custom BizTalk logger)
// - Windows Event Log entries (MESSAGE_INFO, SYSTEM_EXCEPTION_ERREUR, etc.)
// - FILE send ports: STM.ICUTexto.EAI_Erreur and STM.ICUTexto.EAI_ResultatExecution
// Adds:
// - Structured log events: sms.send.attempt, sms.send.result, sms.status.poll,
// sms.status.result, sms.error (defined in future-state spec)
// - Distributed trace correlation via X-Correlation-Id propagation
// - KQL-queryable custom events replacing FILE-based result archive
// =============================================================================
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: appInsightsName
location: location
kind: 'web'
tags: {
environment: environment
application: 'stm-icutexto-eai'
migrationSource: 'biztalk'
}
properties: {
Application_Type: 'web'
WorkspaceResourceId: logWorkspace.id
RetentionInDays: logRetentionDays
IngestionMode: 'LogAnalytics'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
}
}
// Application Insights alert: DLQ error threshold
// Replaces: BizTalk suspended queue monitoring (manual)
resource dlqAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
name: '${appInsightsName}-dlq-alert'
location: 'global'
tags: {
environment: environment
}
properties: {
description: 'sms-outbound DLQ message count exceeds threshold — investigate fn-envoyer-sms failures'
severity: 2
enabled: true
scopes: [serviceBus.id]
evaluationFrequency: 'PT5M'
windowSize: 'PT15M'
criteria: {
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
allOf: [
{
name: 'DLQCount'
metricName: 'DeadLetteredMessages'
metricNamespace: 'Microsoft.ServiceBus/namespaces'
operator: 'GreaterThan'
threshold: 10
timeAggregation: 'Maximum'
criterionType: 'StaticThresholdCriterion'
dimensions: [
{
name: 'EntityName'
operator: 'Include'
values: [sbQueueSmsSend]
}
]
}
]
}
autoMitigate: true
actions: [] // Add action group (email/SMS/webhook) per STM alerting standards
}
}
// =============================================================================
// Azure Key Vault
// Replaces:
// - BizTalk SSO credential store (AuthorizationToken, ServicePlanId per Source)
// - Custom EnvoyerSMSVuduHelper.ObtenirHeader() — stored as vudu-header-auth
// - Custom DemanderStatusHelper.ObtenirAuthorizationToken(Source) — vudu-auth-token-{source}
// - Custom DemanderStatusHelper.ObtenirServicePlanId(Source) — vudu-service-plan-{source}
// All Function apps use managed identity (DefaultAzureCredential) — no client secrets.
// Secret names match the illustrative names in the future-state spec.
// =============================================================================
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: keyVaultName
location: location
tags: {
environment: environment
application: 'stm-icutexto-eai'
migrationSource: 'biztalk-sso'
}
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: tenantId
enableRbacAuthorization: true // Use RBAC, not legacy access policies
enableSoftDelete: true
softDeleteRetentionInDays: 90
enablePurgeProtection: true
publicNetworkAccess: 'Enabled' // Restrict to private endpoint in Premium/prod
networkAcls: {
defaultAction: 'Allow' // Tighten to 'Deny' + VNet rules in prod
bypass: 'AzureServices'
}
}
}
// Key Vault secrets — populated post-deploy by STM ops team
// The secret values here are placeholders; actual values must come from
// BizTalk SSO export or STM credential documentation (spec Risk #8).
resource secretVuduHeaderAuth 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'vudu-header-auth'
properties: {
value: 'PLACEHOLDER-replace-with-actual-token' // Replaces EnvoyerSMSVuduHelper.ObtenirHeader()
attributes: {
enabled: true
}
contentType: 'text/plain'
}
}
resource secretVuduProviderUri 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'vudu-provider-uri'
properties: {
value: vuduProviderUri // Replaces BizTalk binding variable ${uriFournisseur}
attributes: {
enabled: true
}
contentType: 'text/plain'
}
}
resource secretIcuSqlConnection 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = if (!empty(icuSqlConnectionString)) {
parent: keyVault
name: 'icu-sql-connection-string'
properties: {
// Replaces ${IcuDbAdress} BizTalk binding variable.
// Prefer managed identity over connection string at implementation time.
value: icuSqlConnectionString
attributes: {
enabled: true
}
contentType: 'text/plain'
}
}
resource secretVuduWebhookSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'vudu-webhook-secret'
properties: {
value: 'PLACEHOLDER-replace-with-vudu-hmac-secret' // Used in APIM policy for /sms/status/callback
attributes: {
enabled: true
}
contentType: 'text/plain'
}
}
// RBAC: Grant Function app managed identity access to Key Vault secrets
// Role: Key Vault Secrets User (4633458b-17de-408a-b874-0445c86b69e6)
resource funcKeyVaultRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(functionAppManagedIdentityObjectId)) {
name: guid(keyVault.id, functionAppManagedIdentityObjectId, '4633458b-17de-408a-b874-0445c86b69e6')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
principalId: functionAppManagedIdentityObjectId
principalType: 'ServicePrincipal'
}
}
// =============================================================================
// Azure Service Bus Namespace + Queues
// Replaces:
// - BizTalk MessageBox (SQL Server pub/sub routing)
// - BizTalk suspended queue (messages that fail processing)
// Queue design follows spec section "Azure Service Bus (Replace BizTalk MessageBox)"
// =============================================================================
resource serviceBus 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' = {
name: serviceBusName
location: location
tags: {
environment: environment
application: 'stm-icutexto-eai'
migrationSource: 'biztalk-messagebox'
}
sku: {
name: serviceBusSku
tier: serviceBusSku
}
properties: {
minimumTlsVersion: '1.2'
disableLocalAuth: false // Set true once managed identity is confirmed working
zoneRedundant: serviceBusSku == 'Premium'
}
}
// sms-outbound queue
// Replaces: MessageBox publish from Mediator orchestration → EnvoyerSMS
// DLQ replaces: BizTalk suspended queue for unprocessable SMS send messages
resource queueSmsOutbound 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
parent: serviceBus
name: sbQueueSmsSend
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: false // No ordered delivery required on outbound queue
defaultMessageTimeToLive: 'PT4H'
deadLetteringOnMessageExpiration: true
maxDeliveryCount: 3 // Matches retry policy: 3 attempts
enableBatchedOperations: true
enablePartitioning: false
}
}
// sms-dispatch queue (internal: fn-envoyer-sms → fn-envoyer-sms-vudu)
// This is the internal handoff queue between filter/validate step and Vudu dispatch.
// No BizTalk equivalent — BizTalk used a direct 'call' (blocking); this decouples them.
resource queueSmsDispatch 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
parent: serviceBus
name: sbQueueSmsDispatch
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: false
defaultMessageTimeToLive: 'PT4H'
deadLetteringOnMessageExpiration: true
maxDeliveryCount: 3
enableBatchedOperations: true
enablePartitioning: false
}
}
// sms-status-request queue
// Replaces: MessageBox publish from MettreAJourStatus → DemanderStatus
// Sessions disabled per spec (unless fan-out parallelism needed at scale)
resource queueSmsStatus 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
parent: serviceBus
name: sbQueueSmsStatus
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: false
defaultMessageTimeToLive: 'PT2H'
deadLetteringOnMessageExpiration: true
maxDeliveryCount: 3
enableBatchedOperations: true
enablePartitioning: false
}
}
// sms-inbound queue
// Replaces: MessageBox routing of MessageUsagerEntrant from ReceptionVudu receive location
// Phase 2 — consumers of this queue are out of scope for Phase 1
resource queueSmsInbound 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
parent: serviceBus
name: sbQueueSmsInbound
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: false
defaultMessageTimeToLive: 'PT24H'
deadLetteringOnMessageExpiration: true
maxDeliveryCount: 5
enableBatchedOperations: true
enablePartitioning: false
}
}
// sms-extraction queue
// Replaces: MessageBox publish from ExtraireSSPO orchestration
// Phase 2 — out of scope for Phase 1 but provisioned for readiness
resource queueSmsExtraction 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
parent: serviceBus
name: sbQueueSmsExtraction
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 2048
requiresDuplicateDetection: false
requiresSession: false
defaultMessageTimeToLive: 'PT24H'
deadLetteringOnMessageExpiration: true
maxDeliveryCount: 3
enableBatchedOperations: true
enablePartitioning: false
}
}
// sms-sql-write queue — sessions enabled for ordered delivery
// Replaces: STM.ICUTexto.EAI_SQLTraitementSms send port (OrderedDelivery=true)
// Session key = IdMessage to serialize writes per message (spec Risk #5)
resource queueSmsSqlWrite 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
parent: serviceBus
name: 'sms-sql-write'
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: true // CRITICAL: Sessions = ordered delivery per IdMessage
defaultMessageTimeToLive: 'PT4H'
deadLetteringOnMessageExpiration: true
maxDeliveryCount: 3
enableBatchedOperations: false // Must be false when requiresSession = true
enablePartitioning: false
}
}
// =============================================================================
// Azure Storage Account (required by Azure Functions runtime)
// =============================================================================
resource functionStorage 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: functionStorageName
location: location
tags: {
environment: environment
application: 'stm-icutexto-eai'
}
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
supportsHttpsTrafficOnly: true
}
}
// =============================================================================
// Azure Functions — App Service Plan
// Y1 = Consumption (serverless, cost-efficient for variable SMS traffic)
// EP1/EP2/EP3 = Premium (required if VNet injection needed for SQL private endpoint)
// =============================================================================
resource functionPlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: functionPlanName
location: location
tags: {
environment: environment
application: 'stm-icutexto-eai'
migrationSource: 'biztalk-host' // Replaces BizTalk Processing Host Instance
}
sku: {
name: functionsPlanSku
tier: functionsPlanSku == 'Y1' ? 'Dynamic' : 'ElasticPremium'
}
kind: 'functionapp'
properties: {
reserved: false // Windows — set true for Linux
}
}
// =============================================================================
// Azure Functions App
// Replaces: BizTalk orchestration engine + all orchestrations:
// - EnvoyerSMS (fn-envoyer-sms)
// - EnvoyerSMSVudu (fn-envoyer-sms-vudu — Durable Function)
// - DemanderStatus (fn-demander-status)
// - MettreAJourStatus (fn-mettre-a-jour-status — timer trigger)
// - ExtraireSSPO (fn-extraire-sspo — timer/HTTP trigger)
// - Purge (fn-purger — timer trigger, replaces ScheduledTaskAdapter)
//
// All functions use system-assigned managed identity for Key Vault + SQL access.
// DefaultAzureCredential in Function code resolves to this identity at runtime.
// =============================================================================
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
name: functionAppName
location: location
kind: 'functionapp'
tags: {
environment: environment
application: 'stm-icutexto-eai'
migrationSource: 'biztalk-orchestration-engine'
}
identity: {
type: 'SystemAssigned' // Managed identity — no service principal secrets (replaces BizTalk SSO pattern)
}
properties: {
serverFarmId: functionPlan.id
httpsOnly: true
siteConfig: {
ftpsState: 'Disabled'
minTlsVersion: '1.2'
netFrameworkVersion: 'v8.0'
appSettings: [
// Runtime
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'dotnet-isolated'
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorage.name};AccountKey=${functionStorage.listKeys().keys[0].value};EndpointSuffix=core.windows.net'
}
// Application Insights — replaces STM.ICUTexto.EAI.Helper.Logging.Logger
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsights.properties.ConnectionString
}
{
name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
value: '~3'
}
// Service Bus connection — used by all queue triggers
// Replaces BizTalk MessageBox as the message transport
{
name: 'ServiceBusConnection__fullyQualifiedNamespace'
value: '${serviceBus.name}.servicebus.windows.net'
}
// Key Vault URI — Functions reference secrets via @Microsoft.KeyVault(SecretUri=...)
// Replaces BizTalk SSO lookups in helper assemblies
{
name: 'KEY_VAULT_URI'
value: keyVault.properties.vaultUri
}
// Vudu provider URI — replaces ${uriFournisseur} BizTalk binding variable
{
name: 'VUDU_PROVIDER_URI'
value: '@Microsoft.KeyVault(SecretUri=${keyVault.properties.vaultUri}secrets/vudu-provider-uri/)'
}
// Polling intervals — replace BizTalk binding variables
// ${IntervalPollingBroadcast} and ${IntervalPollingFeed}
// CRON format: "0 */5 * * * *" = every 5 minutes
{
name: 'POLLING_BROADCAST_CRON'
value: '0 */5 * * * *' // Parameterize — map to IntervalPollingBroadcast
}
{
name: 'POLLING_FEED_CRON'
value: '0 */10 * * * *' // Parameterize — map to IntervalPollingFeed
}
// Max polling rows — replaces ${MaxPollingBroadcast} BizTalk binding variable
{
name: 'MAX_POLLING_BROADCAST'
value: '100'
}
// Daily purge CRON — replaces ScheduledTaskAdapter third-party component
{
name: 'PURGE_CRON'
value: '0 0 2 * * *' // 02:00 UTC daily
}
// SQL ICU DB — replaces ${IcuDbAdress} and ${IcuDbAdoAdress}
// Using Key Vault reference; managed identity preferred at runtime
{
name: 'ICU_SQL_CONNECTION_STRING'
value: empty(icuSqlConnectionString) ? '' : '@Microsoft.KeyVault(SecretUri=${keyVault.properties.vaultUri}secrets/icu-sql-connection-string/)'
}
]
}
}
}
// =============================================================================
// Azure API Management Instance
// Replaces:
// - WCF-WebHttp receive location STM.ICUTexto.EAI_EnvoiTexto
// - WCF-WebHttp receive location STM.ICUTexto.EAI_ReceptionVudu
// - Custom STM.ICUTexto.EAI.Helper.Securite.AuthorizationManager
// - Custom STM.WCFHttpHeaderBehavior WCF behavior
// - Custom STM.TLSEndpointBehavior WCF behavior
// APIM provides: JWT validation, rate limiting, correlation ID injection,
// logging to App Insights, request transformation
// =============================================================================
resource apim 'Microsoft.ApiManagement/service@2023-05-01-preview' = {
name: apimName
location: location
tags: {
environment: environment
application: 'stm-icutexto-eai'
migrationSource: 'biztalk-wcf-http'
}
sku: {
name: apimSku
capacity: apimSku == 'Developer' ? 1 : 2
}
identity: {
type: 'SystemAssigned'
}
properties: {
publisherEmail: apimPublisherEmail
publisherName: apimPublisherName
virtualNetworkType: 'None' // Set to 'Internal' or 'External' for Premium VNet injection
customProperties: {
'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10': 'false'
'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11': 'false'
'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10': 'false'
'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11': 'false'
}
}
}
// APIM Logger — connects APIM policies to Application Insights
// Used by log-to-eventhub policy in apim-sms-api.yaml
resource apimLogger 'Microsoft.ApiManagement/service/loggers@2023-05-01-preview' = {
parent: apim
name: 'appinsights-logger'
properties: {
loggerType: 'applicationInsights'
description: 'Application Insights logger for STM ICUTexto SMS API — replaces BizTalk custom Logger'
credentials: {
instrumentationKey: appInsights.properties.InstrumentationKey
}
isBuffered: true
resourceId: appInsights.id
}
}
// APIM Named Value: Vudu webhook secret (references Key Vault)
// Used by check-header policy in /sms/status/callback inbound policy
resource apimNamedValueVuduSecret 'Microsoft.ApiManagement/service/namedValues@2023-05-01-preview' = {
parent: apim
name: 'vudu-webhook-secret'
properties: {
displayName: 'vudu-webhook-secret'
secret: true
keyVault: {
secretIdentifier: '${keyVault.properties.vaultUri}secrets/vudu-webhook-secret'
}
}
dependsOn: [secretVuduWebhookSecret]
}
// APIM Named Value: Azure AD Tenant ID (used in validate-jwt policy)
resource apimNamedValueTenantId 'Microsoft.ApiManagement/service/namedValues@2023-05-01-preview' = {
parent: apim
name: 'tenant-id'
properties: {
displayName: 'tenant-id'
secret: false
value: tenantId
}
}
// APIM Backend: sms-outbound-function
// Points APIM to the Azure Function app for POST /sms/send
// Replaces: BizTalk send port STM.ICUTexto.EAI_EnvoiTextoResponse
resource apimBackendSmsOutbound 'Microsoft.ApiManagement/service/backends@2023-05-01-preview' = {
parent: apim
name: 'sms-outbound-function'
properties: {
description: 'fn-envoyer-sms — replaces EnvoyerSMS BizTalk orchestration trigger'
url: 'https://${functionApp.properties.defaultHostName}/api'
protocol: 'http'
tls: {
validateCertificateChain: true
validateCertificateName: true
}
credentials: {
header: {
// Function key auth — replace with managed identity when using Easy Auth
'x-functions-key': ['{{function-app-host-key}}']
}
}
}
}
// APIM Backend: sms-batch-function
// Points APIM to the Azure Durable Function for POST /sms/send-batch
// Replaces: EnvoyerSMSVudu sub-orchestration (batch path)
resource apimBackendSmsBatch 'Microsoft.ApiManagement/service/backends@2023-05-01-preview' = {
parent: apim
name: 'sms-batch-function'
properties: {
description: 'fn-envoyer-sms-vudu — replaces EnvoyerSMSVudu BizTalk Durable Function'
url: 'https://${functionApp.properties.defaultHostName}/api'
protocol: 'http'
tls: {
validateCertificateChain: true
validateCertificateName: true
}
credentials: {
header: {
'x-functions-key': ['{{function-app-host-key}}']
}
}
}
}
// APIM Backend: sms-status-function
// Points to fn-demander-status for GET /sms/status/{messageId}
// Replaces: DemanderStatus + MettreAJourStatus orchestrations
resource apimBackendSmsStatus 'Microsoft.ApiManagement/service/backends@2023-05-01-preview' = {
parent: apim
name: 'sms-status-function'
properties: {
description: 'fn-demander-status — replaces DemanderStatus and MettreAJourStatus BizTalk orchestrations'
url: 'https://${functionApp.properties.defaultHostName}/api'
protocol: 'http'
tls: {
validateCertificateChain: true
validateCertificateName: true
}
credentials: {
header: {
'x-functions-key': ['{{function-app-host-key}}']
}
}
}
}
// APIM Backend: sms-inbound-function
// Points to fn-reception-sms for POST /sms/status/callback
// Replaces: STM.ICUTexto.EAI_ReceptionVudu receive location
resource apimBackendSmsInbound 'Microsoft.ApiManagement/service/backends@2023-05-01-preview' = {
parent: apim
name: 'sms-inbound-function'
properties: {
description: 'fn-reception-sms — replaces STM.ICUTexto.EAI_ReceptionVudu BizTalk receive location'
url: 'https://${functionApp.properties.defaultHostName}/api'
protocol: 'http'
tls: {
validateCertificateChain: true
validateCertificateName: true
}
credentials: {
header: {
'x-functions-key': ['{{function-app-host-key}}']
}
}
}
}
// =============================================================================
// APIM API — SMS API (imported from OpenAPI spec)
// Replaces: All BizTalk WCF-WebHttp receive locations for the ICUTexto application
// The OpenAPI spec (apim-sms-api.yaml) defines the full contract.
// =============================================================================
resource apimApi 'Microsoft.ApiManagement/service/apis@2023-05-01-preview' = {
parent: apim
name: 'stm-icutexto-sms'
properties: {
displayName: 'STM ICUTexto SMS API'
description: 'SMS send, status, and webhook — migrated from STM.ICUTexto.EAI BizTalk application'
path: 'icutexto'
protocols: ['https'] // HTTPS only — replaces TransportClientCredentialType=Basic over HTTP
subscriptionRequired: false // JWT validation enforced in policies instead
apiType: 'http'
// Import from OpenAPI spec file
// In production: use contentValue with the actual YAML content or a storage URL
// format: 'openapi'
// contentValue: loadTextContent(openApiSpecPath) // Uncomment when deploying with az bicep
}
}
// =============================================================================
// APIM Global Policy
// Applies to all APIs — sets global correlation ID and App Insights logging
// Replaces: BizTalk tracking and logging infrastructure
// =============================================================================
resource apimGlobalPolicy 'Microsoft.ApiManagement/service/policies@2023-05-01-preview' = {
parent: apim
name: 'policy'
properties: {
format: 'rawxml'
value: '''
<policies>
<inbound>
<!-- Inject correlation ID on every request — propagated through Service Bus and SQL -->
<!-- Replaces ad-hoc tracking in BizTalk orchestrations -->
<set-header name="X-Correlation-Id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<!-- Enforce HTTPS — replaces TransportClientCredentialType configuration -->
<choose>
<when condition="@(context.Request.Url.Scheme != "https")">
<return-response>
<set-status code="400" reason="HTTPS required"/>
</return-response>
</when>
</choose>
</inbound>
<backend>
<forward-request timeout="30"/>
</backend>
<outbound>
<!-- Propagate correlation ID in all responses -->
<set-header name="X-Correlation-Id" exists-action="override">
<value>@(context.RequestId)</value>
</set-header>
<!-- Remove internal headers from responses -->
<set-header name="X-Powered-By" exists-action="delete"/>
<set-header name="X-AspNet-Version" exists-action="delete"/>
</outbound>
<on-error>
<!-- Structured error logging to Application Insights -->
<!-- Replaces STM.Logger.MESSAGE_INFO / SYSTEM_EXCEPTION_ERREUR constants -->
<log-to-eventhub logger-id="appinsights-logger">
@(string.Format("sms.error|correlationId={0}|operation={1}|statusCode={2}|message={3}",
context.RequestId,
context.Operation.Id,
context.Response.StatusCode,
context.LastError.Message))
</log-to-eventhub>
</on-error>
</policies>
'''
}
}
// APIM Product — groups the SMS API under an access-controlled product
// Callers subscribe to this product via Azure AD client credentials
resource apimProduct 'Microsoft.ApiManagement/service/products@2023-05-01-preview' = {
parent: apim
name: 'icutexto-sms'
properties: {
displayName: 'ICUTexto SMS'
description: 'Access to STM ICUTexto SMS send and status APIs — migrated from BizTalk WCF-WebHttp'
subscriptionRequired: false // Auth via JWT, not APIM subscription keys
state: 'published'
approvalRequired: false
}
}
resource apimProductApi 'Microsoft.ApiManagement/service/products/apis@2023-05-01-preview' = {
parent: apimProduct
name: 'stm-icutexto-sms'
dependsOn: [apimApi]
}
// =============================================================================
// Outputs — values needed for post-deployment configuration and Function code
// =============================================================================
@description('APIM gateway URL — provide this to internal callers replacing BizTalk WCF endpoint')
output apimGatewayUrl string = apim.properties.gatewayUrl
@description('Application Insights connection string — set in Function app and any logging clients')
output appInsightsConnectionString string = appInsights.properties.ConnectionString
@description('Application Insights instrumentation key')
output appInsightsInstrumentationKey string = appInsights.properties.InstrumentationKey
@description('Key Vault URI — use for DefaultAzureCredential secret access in Function code')
output keyVaultUri string = keyVault.properties.vaultUri
@description('Service Bus namespace FQDN — set as ServiceBusConnection__fullyQualifiedNamespace in Function app settings')
output serviceBusFqdn string = '${serviceBus.name}.servicebus.windows.net'
@description('Function app managed identity principal ID — assign Key Vault Secrets User role to this ID')
output functionAppPrincipalId string = functionApp.identity.principalId
@description('Function app default hostname — used for APIM backend URL configuration')
output functionAppHostname string = functionApp.properties.defaultHostName
@description('APIM service name — for post-deploy API import and policy updates')
output apimServiceName string = apim.name
@description('APIM managed identity principal ID — assign Key Vault access for named value secret references')
output apimPrincipalId string = apim.identity.principalId
▶ bicep-email-infrastructure.bicep
// =============================================================================
// bicep-email-infrastructure.bicep
// STM ICUCourriel — Azure Infrastructure (replaces BizTalk Server application)
//
// Deployment scope: Resource Group
// Estimated monthly cost (prod): ~$400–600 CAD depending on APIM tier and
// Logic Apps execution volume.
//
// Usage:
// az deployment group create \
// --resource-group rg-stm-icucourriel-prod \
// --template-file bicep-email-infrastructure.bicep \
// --parameters @bicep-email-infrastructure.parameters.json
//
// Parameters file must supply: tenantId, entraAudience, sendGridWebhookPublicKey,
// sqlServerFqdn, sqlDatabaseName, exchangeClientId, exchangeClientSecret,
// sendGridApiKey, logicAppSendEmailTriggerUrl, durableFunctionBaseUrl
// =============================================================================
// ---------------------------------------------------------------------------
// Parameters
// ---------------------------------------------------------------------------
@description('Azure region for all resources.')
param location string = resourceGroup().location
@description('Environment tag: dev | test | prod')
@allowed(['dev', 'test', 'prod'])
param environment string = 'prod'
@description('Microsoft Entra ID tenant ID (used in APIM JWT validation policy).')
param tenantId string
@description('Entra ID audience claim value for the ICUCourriel API.')
param entraAudience string = 'api://stm-icucourriel'
@description('SendGrid Event Webhook ECDSA signing public key (from SendGrid dashboard).')
@secure()
param sendGridWebhookPublicKey string
@description('SendGrid API key (replaces ObtenirSendGridAPIKey() from SSO helper).')
@secure()
param sendGridApiKey string
@description('Exchange Online OAuth client ID (extracted from TraiterRejetsHelper).')
@secure()
param exchangeClientId string
@description('Exchange Online OAuth client secret (extracted from TraiterRejetsHelper).')
@secure()
param exchangeClientSecret string
@description('Fully qualified domain name of the ICU SQL Server (e.g. sql-stm-icu.database.windows.net).')
param sqlServerFqdn string
@description('ICU SQL database name (unchanged from BizTalk — same schema retained).')
param sqlDatabaseName string
@description('Logic Apps Standard HTTP trigger URL for the SendEmail workflow.')
@secure()
param logicAppSendEmailTriggerUrl string
@description('Durable Function HTTP starter base URL for the ProcessRejections orchestrator.')
@secure()
param durableFunctionBaseUrl string
@description('APIM publisher email (required for APIM provisioning).')
param apimPublisherEmail string = 'integration@stm.info'
@description('APIM publisher name.')
param apimPublisherName string = 'STM Integration'
// ---------------------------------------------------------------------------
// Variables / naming
// ---------------------------------------------------------------------------
var prefix = 'stm-icucourriel'
var prefixNoDash = 'stmicucourriel'
var tags = {
application: 'ICUCourriel'
environment: environment
migratedFrom: 'BizTalk Server'
owner: 'STM Integration Team'
}
// =============================================================================
// APPLICATION INSIGHTS
//
// Replaces: STM.ICUCourriel.EAI.Helper.Logging.Logger (custom event-log helper)
// with BizTalk TrackingOption="None" (all tracking disabled).
//
// All three Azure services (APIM, Logic Apps Standard, Durable Functions)
// are configured to use this single Application Insights instance so that
// distributed traces, correlation IDs (x-correlation-id header), and
// structured log events all land in the same workspace.
// =============================================================================
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
name: '${prefix}-law-${environment}'
location: location
tags: tags
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: 90
}
}
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: '${prefix}-ai-${environment}'
location: location
tags: tags
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: logAnalyticsWorkspace.id
// Sampling: start at 100% during parallel-run, reduce after BizTalk decommission
SamplingPercentage: 100
}
}
// ---------------------------------------------------------------------------
// Alert: Email failure rate threshold
// Replaces: No monitoring equivalent in BizTalk (TrackingOption="None").
// ---------------------------------------------------------------------------
resource failureRateAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
name: '${prefix}-alert-echec-rate-${environment}'
location: 'global'
tags: tags
properties: {
description: 'Alert when email Statut=Echec rate exceeds 5% in a 5-minute window'
severity: 2
enabled: true
scopes: [appInsights.id]
evaluationFrequency: 'PT1M'
windowSize: 'PT5M'
criteria: {
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
allOf: [
{
name: 'EchecRateThreshold'
metricName: 'exceptions/count'
metricNamespace: 'microsoft.insights/components'
operator: 'GreaterThan'
threshold: 10
timeAggregation: 'Count'
criterionType: 'StaticThresholdCriterion'
}
]
}
autoMitigate: true
}
}
// =============================================================================
// AZURE KEY VAULT
//
// Replaces: BizTalk Enterprise SSO affiliate application (AffiliateApplicationName
// was blank in production bindings — secrets lived inside the compiled
// STM.ICUCourriel.EAI.Helper assembly).
//
// Secrets stored:
// sendgrid-api-key ← ObtenirSendGridAPIKey() return value
// exchange-tenant-id ← ExchangeOnlinePropertySchema.tenantId runtime value
// exchange-client-id ← client_id from TraiterRejetsHelper.ObtenirTokenMessage()
// exchange-client-secret ← client_secret from same (MUST be extracted before Phase 2)
// icudb-connection-string ← ${IcuDbAdress} BizTalk binding property
// sendgrid-webhook-pubkey ← SendGrid Event Webhook signing key (new)
//
// All Logic App and Durable Function access is via managed identity —
// no secrets in application settings or environment variables.
// =============================================================================
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: '${prefixNoDash}kv${environment}' // Key Vault name: max 24 chars, alphanumeric+dash
location: location
tags: tags
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
// RBAC authorization replaces legacy access policies
enableRbacAuthorization: true
enableSoftDelete: true
softDeleteRetentionInDays: 90
enablePurgeProtection: true
// Network ACLs: restrict to APIM and Logic Apps VNet if using VNet integration
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
// Add Logic Apps / Durable Functions subnet rules when VNet integration is enabled
ipRules: []
virtualNetworkRules: []
}
}
}
// SendGrid API key — replaces ObtenirSendGridAPIKey() from helper assembly
resource secretSendGridApiKey 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'sendgrid-api-key'
properties: {
value: sendGridApiKey
contentType: 'application/x-api-key'
attributes: {
enabled: true
}
}
}
// Exchange tenant ID — replaces BizTalk context property injection
resource secretExchangeTenantId 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'exchange-tenant-id'
properties: {
value: tenantId
contentType: 'text/plain'
attributes: {
enabled: true
}
}
}
// Exchange OAuth client credentials — extracted from TraiterRejetsHelper.
// ACTION REQUIRED (Risk #3): Extract these before Phase 2 migration.
// Once managed identity is granted Mail.Send on the Exchange app registration,
// exchange-client-id and exchange-client-secret can be deleted from Key Vault —
// the ObtenirTokenOAuth orchestration is fully eliminated.
resource secretExchangeClientId 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'exchange-client-id'
properties: {
value: exchangeClientId
contentType: 'text/plain'
attributes: {
enabled: true
}
}
}
resource secretExchangeClientSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'exchange-client-secret'
properties: {
value: exchangeClientSecret
contentType: 'text/plain'
attributes: {
enabled: true
}
}
}
// ICU database connection string — replaces ${IcuDbAdress} BizTalk binding
resource secretIcuDbConnectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'icudb-connection-string'
properties: {
value: 'Server=${sqlServerFqdn};Database=${sqlDatabaseName};Authentication=Active Directory Managed Identity;Encrypt=True;'
contentType: 'application/x-connection-string'
attributes: {
enabled: true
}
}
}
// SendGrid Event Webhook ECDSA signing public key (new — no BizTalk equivalent)
resource secretSendGridWebhookKey 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'sendgrid-webhook-pubkey'
properties: {
value: sendGridWebhookPublicKey
contentType: 'text/plain'
attributes: {
enabled: true
}
}
}
// Logic Apps HTTP trigger URL for SendEmail workflow (sensitive — contains SAS token)
resource secretLogicAppTriggerUrl 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'logicapp-sendemail-trigger-url'
properties: {
value: logicAppSendEmailTriggerUrl
contentType: 'text/uri'
attributes: {
enabled: true
}
}
}
// Durable Function HTTP starter URL
resource secretDurableFunctionUrl 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'durable-fn-rejections-url'
properties: {
value: durableFunctionBaseUrl
contentType: 'text/uri'
attributes: {
enabled: true
}
}
}
// =============================================================================
// AZURE SERVICE BUS
//
// Replaces: BizTalk MessageBox (SQL Server database used as the central
// publish/subscribe message broker for all BizTalk orchestrations).
//
// Queue/topic mapping:
// email-send-queue ← MessageUsagerCourriel published to MessageBox
// (session-enabled for ordered delivery —
// see Risk #8: SQL send port OrderedDelivery=true)
// email-rejection-queue ← Trigger for TraiterRejets (was: FILE drop-folder)
// email-deadletter ← BizTalk suspended queue (no manual Admin Console needed)
// email-results (topic) ← STM.ICUCourriel.EAI_ResultatExecution FILE send port
// └── subscription: all ← Default catch-all subscriber
// =============================================================================
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2023-01-01-preview' = {
name: '${prefix}-sb-${environment}'
location: location
tags: tags
sku: {
name: 'Standard'
// Upgrade to Premium if VNet integration is required (private endpoint for SQL/Key Vault)
tier: 'Standard'
}
properties: {
zoneRedundant: false // Set true for production Premium tier
}
}
// ---------------------------------------------------------------------------
// email-send-queue
// Replaces: MessageUsagerCourriel messages published to BizTalk MessageBox.
// Sessions enabled to preserve ordered delivery per idMessage (Risk #8).
// ---------------------------------------------------------------------------
resource emailSendQueue 'Microsoft.ServiceBus/namespaces/queues@2023-01-01-preview' = {
parent: serviceBusNamespace
name: 'email-send-queue'
properties: {
requiresSession: true // Preserves ordered delivery (Risk #8 mitigation)
maxDeliveryCount: 3 // Matches BizTalk send port retry count (3 retries)
lockDuration: 'PT2M' // 2-minute processing window per Logic App run
defaultMessageTimeToLive: 'P1D' // 1 day TTL — messages not consumed after 1 day go to DLQ
deadLetteringOnMessageExpiration: true
enablePartitioning: false
}
}
// ---------------------------------------------------------------------------
// email-rejection-queue
// Replaces: FILE receive location that triggered TraiterRejets orchestration
// (ScheduledTaskAdapter drop-folder trigger).
// Also receives SendGrid Event Webhook events for near-real-time processing.
// ---------------------------------------------------------------------------
resource emailRejectionQueue 'Microsoft.ServiceBus/namespaces/queues@2023-01-01-preview' = {
parent: serviceBusNamespace
name: 'email-rejection-queue'
properties: {
requiresSession: false
maxDeliveryCount: 5
lockDuration: 'PT5M' // Longer lock for reconciliation activities
defaultMessageTimeToLive: 'P7D' // 7 days — give ops team time to replay DLQ items
deadLetteringOnMessageExpiration: true
enablePartitioning: false
}
}
// ---------------------------------------------------------------------------
// email-deadletter (explicit dead-letter staging queue)
// Replaces: BizTalk suspended queue (required BizTalk Admin Console to inspect).
// Service Bus dead-letter queues are accessible via standard SDK and
// Azure Portal without any specialized tooling.
// ---------------------------------------------------------------------------
resource emailDeadLetterQueue 'Microsoft.ServiceBus/namespaces/queues@2023-01-01-preview' = {
parent: serviceBusNamespace
name: 'email-deadletter'
properties: {
requiresSession: false
maxDeliveryCount: 1 // Manual replay only — no automatic retry from DLQ
defaultMessageTimeToLive: 'P30D'
enablePartitioning: false
}
}
// ---------------------------------------------------------------------------
// email-results (topic)
// Replaces: STM.ICUCourriel.EAI_ResultatExecution FILE send port.
// BizTalk wrote XML ResultatExecution files to ${ResultatExecutionRepertoire}.
// Downstream consumers now subscribe to this topic — no shared filesystem.
// ---------------------------------------------------------------------------
resource emailResultsTopic 'Microsoft.ServiceBus/namespaces/topics@2023-01-01-preview' = {
parent: serviceBusNamespace
name: 'email-results'
properties: {
defaultMessageTimeToLive: 'P1D'
enablePartitioning: false
requiresDuplicateDetection: true
duplicateDetectionHistoryTimeWindow: 'PT10M'
}
}
// Default catch-all subscription — replaces BizTalk MessageBox filter
// on BTS.MessageType = 'ResultatExecution' AND Medium = 'Courriel'
resource emailResultsSubscriptionAll 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2023-01-01-preview' = {
parent: emailResultsTopic
name: 'all'
properties: {
maxDeliveryCount: 3
lockDuration: 'PT1M'
defaultMessageTimeToLive: 'P1D'
}
}
// ---------------------------------------------------------------------------
// Service Bus diagnostic settings → Application Insights
// ---------------------------------------------------------------------------
resource sbDiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
name: 'sb-to-appinsights'
scope: serviceBusNamespace
properties: {
workspaceId: logAnalyticsWorkspace.id
logs: [
{
categoryGroup: 'allLogs'
enabled: true
retentionPolicy: {
enabled: false
days: 0
}
}
]
metrics: [
{
category: 'AllMetrics'
enabled: true
}
]
}
}
// =============================================================================
// AZURE API MANAGEMENT
//
// Replaces: BizTalk WCF-WebHttp receive location on port 443 at
// STM.ICUCourriel.EAI_ReceptionCourriel, plus the custom
// AuthorizationManager compiled into STM.ICUCourriel.EAI.Helper.
//
// APIM tier: Developer for dev/test, Standard v2 for prod.
// Standard v2 supports VNet injection, managed identity, and Application
// Insights integration without the Premium price.
// =============================================================================
resource apim 'Microsoft.ApiManagement/service@2023-09-01-preview' = {
name: '${prefix}-apim-${environment}'
location: location
tags: tags
sku: {
name: environment == 'prod' ? 'StandardV2' : 'Developer'
capacity: 1
}
identity: {
// System-assigned managed identity used to:
// 1. Forward requests to Logic Apps (authentication-managed-identity policy)
// 2. Read named values from Key Vault (Key Vault named value references)
type: 'SystemAssigned'
}
properties: {
publisherEmail: apimPublisherEmail
publisherName: apimPublisherName
notificationSenderEmail: 'apimnotifications@stm.info'
customProperties: {
'Microsoft.WindowsAzure.ApiManagement.Gateway.Protocols.Server.Http2': 'true'
}
}
}
// ---------------------------------------------------------------------------
// Key Vault RBAC: Grant APIM managed identity Key Vault Secrets User
// (read-only access to secrets — named values resolved at request time)
// ---------------------------------------------------------------------------
resource apimKvRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, apim.id, 'Key Vault Secrets User')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User
principalId: apim.identity.principalId
principalType: 'ServicePrincipal'
}
}
// ---------------------------------------------------------------------------
// APIM Named Values (Key Vault references)
// These are referenced in APIM policies as {{named-value-name}}.
// Secrets are fetched from Key Vault at policy evaluation time —
// they never appear in APIM configuration in plaintext.
// ---------------------------------------------------------------------------
resource nvLogicAppTriggerUrl 'Microsoft.ApiManagement/service/namedValues@2023-09-01-preview' = {
parent: apim
name: 'logicapp-sendemail-trigger-url'
properties: {
displayName: 'logicapp-sendemail-trigger-url'
secret: true
keyVault: {
secretIdentifier: secretLogicAppTriggerUrl.properties.secretUri
}
}
}
resource nvDurableFnUrl 'Microsoft.ApiManagement/service/namedValues@2023-09-01-preview' = {
parent: apim
name: 'durable-fn-rejections-url'
properties: {
displayName: 'durable-fn-rejections-url'
secret: true
keyVault: {
secretIdentifier: secretDurableFunctionUrl.properties.secretUri
}
}
}
resource nvSendGridWebhookKey 'Microsoft.ApiManagement/service/namedValues@2023-09-01-preview' = {
parent: apim
name: 'sendgrid-webhook-public-key'
properties: {
displayName: 'sendgrid-webhook-public-key'
secret: true
keyVault: {
secretIdentifier: secretSendGridWebhookKey.properties.secretUri
}
}
}
resource nvTenantId 'Microsoft.ApiManagement/service/namedValues@2023-09-01-preview' = {
parent: apim
name: 'entra-tenant-id'
properties: {
displayName: 'entra-tenant-id'
secret: false // Tenant ID is not sensitive
value: tenantId
}
}
// ---------------------------------------------------------------------------
// Application Insights integration for APIM
// Every APIM request/response is logged with operation ID = x-correlation-id.
// ---------------------------------------------------------------------------
resource apimAppInsightsLogger 'Microsoft.ApiManagement/service/loggers@2023-09-01-preview' = {
parent: apim
name: 'appinsights-logger'
properties: {
loggerType: 'applicationInsights'
description: 'Application Insights logger for APIM — replaces BizTalk event log'
credentials: {
instrumentationKey: appInsights.properties.InstrumentationKey
}
isBuffered: true
}
}
resource apimDiagnostics 'Microsoft.ApiManagement/service/diagnostics@2023-09-01-preview' = {
parent: apim
name: 'applicationinsights'
properties: {
loggerId: apimAppInsightsLogger.id
alwaysLog: 'allErrors'
sampling: {
samplingType: 'fixed'
percentage: 100
}
frontend: {
request: {
headers: ['x-correlation-id', 'Content-Type', 'Content-Length']
body: {
bytes: 0 // Do not log request body (PII risk)
}
}
response: {
headers: ['x-correlation-id', 'Content-Type']
}
}
backend: {
request: {
headers: ['x-correlation-id']
}
}
}
}
// ---------------------------------------------------------------------------
// APIM API definition — ICUCourriel Email API
//
// The full OpenAPI 3.0 spec (apim-email-api.yaml) is the authoritative
// contract. This Bicep resource registers the API in APIM and attaches
// the product-level and operation-level policies.
// ---------------------------------------------------------------------------
resource emailApi 'Microsoft.ApiManagement/service/apis@2023-09-01-preview' = {
parent: apim
name: 'icucourriel-email-api'
properties: {
displayName: 'STM ICUCourriel Email API'
description: 'Email dispatch and rejection reconciliation. Replaces BizTalk STM.ICUCourriel.EAI WCF-WebHttp receive location.'
serviceUrl: '{{logicapp-sendemail-trigger-url}}' // Backend base URL (overridden per-operation)
path: 'icucourriel/v1'
protocols: ['https']
subscriptionRequired: false // Auth via OAuth 2.0 Bearer, not APIM subscription keys
apiType: 'http'
// Import from OpenAPI spec file in CI/CD pipeline:
// format: 'openapi+json-link'
// value: 'https://<storage>/apim-email-api.yaml'
}
}
// ---------------------------------------------------------------------------
// API-level inbound policy (applies to all operations)
//
// Replaces:
// - Custom AuthorizationManager in STM.ICUCourriel.EAI.Helper (JWT validation)
// - WCF-WebHttp receive location 202 KB message size cap (now 10 MB)
// - No correlation tracking existed in BizTalk (TrackingOption="None")
// ---------------------------------------------------------------------------
resource emailApiPolicy 'Microsoft.ApiManagement/service/apis/policies@2023-09-01-preview' = {
parent: emailApi
name: 'policy'
properties: {
format: 'xml'
value: '''
<policies>
<inbound>
<base />
<!--
JWT Validation
Replaces: Custom AuthorizationManager compiled into
STM.ICUCourriel.EAI.Helper.dll. The BizTalk receive location used
Basic auth; this policy enforces OAuth 2.0 client credentials tokens
issued by Microsoft Entra ID against the api://stm-icucourriel audience.
-->
<validate-jwt header-name="Authorization"
failed-validation-httpcode="401"
failed-validation-error-message="Unauthorized: valid Entra ID token required">
<openid-config url="https://login.microsoftonline.com/{{entra-tenant-id}}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://stm-icucourriel</audience>
</audiences>
<required-claims>
<claim name="aud" match="all">
<value>api://stm-icucourriel</value>
</claim>
</required-claims>
</validate-jwt>
<!--
Payload Size Guard
Replaces: 202 KB cap enforced by BizTalk WCF-WebHttp receive location
binding configuration. New cap: 10 MB (10,485,760 bytes).
-->
<choose>
<when condition="@(context.Request.Headers.GetValueOrDefault("Content-Length", "0").As<int>() > 10485760)">
<return-response>
<set-status code="413" reason="Payload Too Large" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>{"code":"PAYLOAD_TOO_LARGE","message":"Request body exceeds the 10 MB limit."}</set-body>
</return-response>
</when>
</choose>
<!--
Correlation ID Injection
Replaces: No equivalent in BizTalk (TrackingOption="None" on all services).
The x-correlation-id header is forwarded to Logic Apps and Durable Functions.
All Application Insights telemetry is tagged with this ID for end-to-end tracing.
-->
<set-header name="x-correlation-id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
<!--
Rate Limiting
Replaces: Max 200 concurrent calls on BizTalk receive location.
200 calls per 60-second window per caller (subscription ID or JWT sub claim).
Adjust per caller during load testing (Risk #10).
-->
<rate-limit-by-key calls="200"
renewal-period="60"
counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization", "anonymous"))"
increment-condition="@(true)" />
</inbound>
<backend>
<base />
<!--
Managed Identity Backend Authentication
Replaces: ObtenirTokenOAuth orchestration (manual OAuth token acquisition
via form-POST to login.microsoftonline.com).
APIM uses its system-assigned managed identity to authenticate to the
Logic Apps Standard HTTP trigger. No client_id/client_secret required.
NOTE: The Logic Apps Standard trigger URL also requires a SAS token;
store it as the named value {{logicapp-sendemail-trigger-url}} so APIM
reads it from Key Vault at runtime.
-->
<authentication-managed-identity resource="https://management.azure.com/" />
</backend>
<outbound>
<base />
<!--
Correlation ID in response — allows callers to correlate request logs
with Application Insights traces without querying the ICU database.
-->
<set-header name="x-correlation-id" exists-action="skip">
<value>@(context.RequestId)</value>
</set-header>
</outbound>
<on-error>
<base />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@{
return new JObject(
new JProperty("code", context.LastError.Source),
new JProperty("message", context.LastError.Message),
new JProperty("correlationId", context.RequestId)
).ToString();
}</set-body>
</on-error>
</policies>
'''
}
}
// ---------------------------------------------------------------------------
// APIM Product — ICUCourriel
// Groups the email API for subscription management.
// ---------------------------------------------------------------------------
resource emailProduct 'Microsoft.ApiManagement/service/products@2023-09-01-preview' = {
parent: apim
name: 'icucourriel'
properties: {
displayName: 'ICUCourriel Email Services'
description: 'Email dispatch and rejection reconciliation APIs — replaces BizTalk STM.ICUCourriel.EAI'
subscriptionRequired: false
state: 'published'
}
}
resource emailProductApi 'Microsoft.ApiManagement/service/products/apis@2023-09-01-preview' = {
parent: emailProduct
name: emailApi.name
}
// =============================================================================
// MANAGED IDENTITY ROLE ASSIGNMENTS
//
// Replaces the ObtenirTokenOAuth BizTalk orchestration entirely.
//
// The ObtenirTokenOAuth orchestration manually acquired OAuth tokens via
// HTTP form-POST to login.microsoftonline.com using a client_id/client_secret
// pair stored in the compiled TraiterRejetsHelper assembly.
//
// In Azure, managed identity eliminates this pattern:
// - Logic Apps Standard system-assigned MI → Key Vault Secrets User
// - Logic Apps Standard system-assigned MI → Service Bus Data Sender
// - Durable Functions system-assigned MI → Key Vault Secrets User
// - Durable Functions system-assigned MI → Service Bus Data Receiver
//
// The MI principal IDs below are placeholders — replace with the actual
// principal IDs from the Logic Apps and Function App deployments.
// These role assignments cannot be fully automated here because Logic Apps
// Standard and Function App are deployed separately (they require their own
// Bicep modules with storage accounts, App Service Plans, etc.).
// =============================================================================
// ---------------------------------------------------------------------------
// Service Bus role assignments (Logic Apps → Service Bus)
// Replaces: BizTalk WCF-SQL send port connecting to MessageBox
// ---------------------------------------------------------------------------
@description('Principal ID of the Logic Apps Standard stm-icucourriel-envoicourriel managed identity.')
param logicAppPrincipalId string = ''
@description('Principal ID of the Durable Function App stm-icucourriel-traiterejets managed identity.')
param durableFunctionPrincipalId string = ''
var sbDataSenderRoleId = '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39' // Azure Service Bus Data Sender
var sbDataReceiverRoleId = '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' // Azure Service Bus Data Receiver
var kvSecretsUserRoleId = '4633458b-17de-408a-b874-0445c86b69e6' // Key Vault Secrets User
resource logicAppSbSenderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(logicAppPrincipalId)) {
name: guid(serviceBusNamespace.id, logicAppPrincipalId, 'SbDataSender')
scope: serviceBusNamespace
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', sbDataSenderRoleId)
principalId: logicAppPrincipalId
principalType: 'ServicePrincipal'
}
}
resource logicAppKvRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(logicAppPrincipalId)) {
name: guid(keyVault.id, logicAppPrincipalId, 'KvSecretsUser')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', kvSecretsUserRoleId)
principalId: logicAppPrincipalId
principalType: 'ServicePrincipal'
}
}
resource durableFnSbReceiverRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(durableFunctionPrincipalId)) {
name: guid(serviceBusNamespace.id, durableFunctionPrincipalId, 'SbDataReceiver')
scope: serviceBusNamespace
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', sbDataReceiverRoleId)
principalId: durableFunctionPrincipalId
principalType: 'ServicePrincipal'
}
}
resource durableFnKvRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(durableFunctionPrincipalId)) {
name: guid(keyVault.id, durableFunctionPrincipalId, 'KvSecretsUser')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', kvSecretsUserRoleId)
principalId: durableFunctionPrincipalId
principalType: 'ServicePrincipal'
}
}
// =============================================================================
// OUTPUTS
// Used by subsequent deployment steps (Logic Apps, Durable Functions, CI/CD).
// =============================================================================
@description('Application Insights connection string — configure in Logic Apps and Function App settings.')
output appInsightsConnectionString string = appInsights.properties.ConnectionString
@description('Application Insights instrumentation key (legacy — prefer connection string).')
output appInsightsInstrumentationKey string = appInsights.properties.InstrumentationKey
@description('Key Vault URI — reference in Logic Apps and Function App settings as @Microsoft.KeyVault(VaultName=...)')
output keyVaultUri string = keyVault.properties.vaultUri
@description('Service Bus namespace fully-qualified hostname.')
output serviceBusHostname string = '${serviceBusNamespace.name}.servicebus.windows.net'
@description('email-send-queue name.')
output emailSendQueueName string = emailSendQueue.name
@description('email-rejection-queue name.')
output emailRejectionQueueName string = emailRejectionQueue.name
@description('email-deadletter queue name.')
output emailDeadLetterQueueName string = emailDeadLetterQueue.name
@description('email-results topic name.')
output emailResultsTopicName string = emailResultsTopic.name
@description('APIM gateway URL.')
output apimGatewayUrl string = apim.properties.gatewayUrl
@description('APIM system-assigned managed identity principal ID — used to grant Key Vault access.')
output apimPrincipalId string = apim.identity.principalId
@description('Email API base URL.')
output emailApiBaseUrl string = '${apim.properties.gatewayUrl}/icucourriel/v1'
Accueil › Intégration
Guide d'intégration
Liste de contrôle — Jour 1
▶
- Obtenir l'accès à la console d'administration BizTalk de la STM (demander via les TI)
- Obtenir l'accès en lecture aux bases de données SQL BizTalk (ICU, Médiateur, Optimax)
- Cloner ce document de référence sur votre poste local
- Consulter l'onglet Vue d'ensemble — comprendre les 6 canaux et les 10 solutions
- Consulter la carte des connexions système — identifier tous les systèmes externes
- Obtenir les justificatifs/accès au tableau Azure DevOps (carnet de migration)
- Identifier votre phase assignée (Phase 0, 1, 2 ou 3)
- Configurer l'environnement de développement local : SDK .NET 8, Azure Functions Core Tools, Azure CLI
Guide de migration — Étapes par orchestration
▶
Pour chaque orchestration à migrer, suivre ces étapes dans l'ordre :
- Analyser : Examiner l'orchestration BizTalk dans Visual Studio — documenter les flux de messages, les formes et les ports
- Mapper les schémas : Exporter les schémas XSD de BizTalk, les convertir en DTO C# ou schémas JSON
- Mapper les transports : Pour chaque port, identifier l'équivalent Azure (voir l'onglet Architecture Azure)
- Mapper l'authentification : Pour chaque justificatif, créer une entrée Key Vault et configurer l'accès Managed Identity
- Construire la Function : Créer une Azure Function avec déclencheur Service Bus, implémenter la logique métier
- Tester la parité : Exécuter en parallèle — mêmes entrées vers BizTalk et Azure, comparer les sorties
- Basculer : Rediriger le trafic de BizTalk vers Azure Function, surveiller pendant 48 h
- Retirer : Désenrôler l'orchestration BizTalk, archiver les artéfacts
12 actions — Préparation de la migration
▶
| # | Action | Responsable | Phase | Priorité |
|---|---|---|---|---|
| 1 | Exporter tous les schémas XSD et les maps BizTalk vers un dépôt Git | Dév. intégration | Pré-Phase 0 | Élevée |
| 2 | Documenter toutes les procédures stockées SQL appelées par les adaptateurs WCF-SQL | ABD / Dév. | Pré-Phase 0 | Élevée |
| 3 | Inventorier toutes les entrées de justificatifs SSO BizTalk → créer un plan de migration Key Vault | Sécurité | Pré-Phase 0 | Élevée |
| 4 | Configurer le groupe de ressources Azure, l'espace de noms Service Bus, le Function App, Key Vault | Infrastructure | Phase 0 | Élevée |
| 5 | Migrer les bases de données SQL ICU vers Azure SQL (ou configurer la connectivité hybride) | ABD | Phase 0 | Élevée |
| 6 | Construire les Azure Functions du pipeline SMS (Phase 1 — canal le plus simple) | Dév. intégration | Phase 1 | Élevée |
| 7 | Contacter VuDu pour la documentation API et l'accès à l'environnement de test | Chef de projet | Phase 1 | Moyenne |
| 8 | Contacter Telus pour la documentation API RVI et les justificatifs de test | Chef de projet | Phase 3 | Moyenne |
| 9 | Valider que le forfait SendGrid supporte le fonctionnement API uniquement (sans SMTP) | Équipe courriel | Phase 2 | Moyenne |
| 10 | Configurer les tableaux de bord App Insights reflétant les vues de la console Admin BizTalk | DevOps | Phase 0 | Moyenne |
| 11 | Créer un harnais de test d'exécution parallèle pour la validation de parité | AQ / Dév. | Phase 1 | Moyenne |
| 12 | Retirer les 4 stubs Recup CBR (aucune migration nécessaire — purement du code mort) | Dév. intégration | Phase 0 | Faible |
Guide des artéfacts
▶
Artéfacts BizTalk à exporter
| Type d'artéfact | Emplacement | Format | Action de migration |
|---|---|---|---|
| Orchestrations (.odx) | Projet Visual Studio | XML/binaire | Réécrire en Azure Functions |
| Schémas (.xsd) | Projet Visual Studio | XSD | Convertir en DTO C# / JSON Schema |
| Maps (.btm) | Projet Visual Studio | XSLT | Remplacer par AutoMapper ou mappage manuel |
| Pipelines (.btp) | Projet Visual Studio | XML | Remplacer par middleware / gestionnaires de messages |
| Fichiers de liaison | Admin BizTalk | XML | Remplacer par la configuration Azure Function |
| Entrées SSO | BD SSO BizTalk | Chiffré | Migrer vers les secrets Key Vault |
| Procédures stockées SQL | Bases de données ICU | T-SQL | Conserver telles quelles ou refactoriser pour Azure SQL |
Référence rapide Azure
▶
Services Azure clés utilisés
| Service | Objectif | Recommandation de SKU |
|---|---|---|
| Azure Functions | Remplacement d'orchestrations, traitement des messages | Premium (EP1) — intégration VNET, pas de démarrage à froid |
| Azure Service Bus | File de messages, pub/sub, livraison ordonnée | Premium — sessions, gros messages, VNET |
| Azure API Management | Passerelle API, auth., limitation de débit | Standard v2 ou Premium si VNET requis |
| Azure SQL | Cible de migration de base de données | Usage général — serverless ou provisionné |
| Azure Key Vault | Secrets, certificats, clés | Standard |
| Application Insights | Surveillance, traçage distribué | Basé sur workspace |
| Azure Blob Storage | Remplacement des dépôts de fichiers, archivage | Standard LRS |
Commandes Azure CLI utiles
# Lister les fonctions du Function App
az functionapp function list --name <app-name> --resource-group <rg>
# Voir les métriques de la file Service Bus
az servicebus queue show --name <queue> --namespace-name <ns> --resource-group <rg>
# Vérifier les secrets Key Vault
az keyvault secret list --vault-name <vault>
# Diffuser les journaux du Function App
az webapp log tail --name <app-name> --resource-group <rg>
Accueil › Feuille de route
Feuille de route de migration
Phase 0 — Fondations et gains rapides
Durée : 2-3 semaines · Effort : Faible · 4 orchestrations
Mettre en place l'infrastructure Azure, retirer le code mort, établir le pipeline CI/CD.
- Retrait : 4 stubs Recup CBR (patron DistribuerMessages rendu obsolète)
- Migration : ExtraireSSPO + SauvegardeExractionSSPO (extraction par lots — simple minuterie)
- Infra : Groupe de ressources, Service Bus, Function App, Key Vault, App Insights
- DevOps : Pipeline CI/CD, gabarits IaC (Bicep), tableaux de bord de surveillance
Dépendances : Abonnement Azure, connectivité réseau vers le SQL sur site (si pas encore migré)
Phase 1 — Canal SMS + Massif
Durée : 4-6 semaines · Effort : Moyen · 9 orchestrations
Migrer le pipeline SMS (canal le plus simple) pour bâtir la confiance de l'équipe et valider les patrons.
- SMS (8 orch.) : Intégration VuDu, envoi unitaire/lot, interrogation de statut, suivi
- Massif (2 orch.) : DistribuerMessages + EnvoiMessage (distribution parallèle)
- Validation : Exécution en parallèle avec BizTalk pendant 2 semaines minimum
- Patron : Établit le gabarit APIM → Service Bus → Function
Dépendances : Infrastructure Phase 0 complétée, justificatifs de test API VuDu
Phase 2 — Courriel + Social + Médiateur
Durée : 8-12 semaines · Effort : Élevé · 23 orchestrations
Phase la plus importante. Migre la plateforme Médiateur centrale, le pipeline Courriel (14 orch.) et le Social (5 orch.).
- Médiateur (5 orch.) : Point d'entrée plateforme, validation GLID, patrons de corrélation
- Courriel (14 orch.) : SendGrid + Exchange + traitement des rebonds + élimination OAuth
- Social (5 orch.) : X/Twitter API v2, limitation de débit, suivi de statut
- Gain clé : ObtenirTokenOAuth éliminé — Managed Identity remplace le flux manuel de jetons
- Gain clé : 4 interrogateurs SendGrid consolidés en 1 fonction timer
Risque : Le Médiateur utilise la livraison ordonnée et la corrélation — nécessite des sessions Service Bus et des tests minutieux.
Phase 3 — Vocal (Telus)
Durée : 6-8 semaines · Effort : Élevé · 7 orchestrations
Canal final et le plus complexe. L'orchestration en convoi Telus RVI nécessite des Durable Functions.
- Vocal (7 orch.) : Appels Telus RVI, gestion des tentatives, vérification d'erreurs, archivage des résultats
- Patron Convoi : EnvoyerTelusAppelVocal → orchestrateur Durable Functions
- Réessai : GererTentative → sous-orchestrateur Durable Functions avec politiques de réessai
- Nettoyage : PurgerCampaign + PurgerQueueAppelVocal → fonctions Timer
Risque : Le patron Convoi n'a pas d'équivalent direct dans Azure. Nécessite une preuve de concept approfondie avant la migration.
Estimation du delta de coût
| Composant | BizTalk (actuel) | Azure (cible) | Delta |
|---|---|---|---|
| Calcul | 2x VM BizTalk (Standard D4s v3) | 1x Function App Premium (EP1) | -40 % |
| Licences | BizTalk Server Enterprise (40 000 $/an) | Inclus dans la consommation Azure | -100 % |
| Messagerie | BizTalk MessageBox (charge SQL) | Service Bus Premium (~700 $/mois) | Nouveau coût |
| Passerelle API | Aucune (points de terminaison WCF directs) | APIM Standard v2 (~300 $/mois) | Nouveau coût |
| Base de données | SQL Server sur site | Azure SQL Usage général (~400 $/mois) | Comparable |
| Surveillance | Admin BizTalk + scripts personnalisés | App Insights + Log Analytics (~100 $/mois) | Meilleur RCI |
| Opérations | 2 ETP partiels (correctifs, surveillance) | ~0,5 ETP (services gérés) | -75 % |
| Estimation annuelle nette | ~85 000 $/an | ~38 000 $/an | -55 % |
Note : Les estimations de coût sont directionnelles et basées sur les tarifs typiques d'Azure Canada Central. Les coûts réels dépendent du volume de messages, du nombre d'exécutions et des ententes d'entreprise négociées.