L’intégration de messagerie Office 365 dans SAP BTP ABAP Environment permet l’envoi d’e-mails directement depuis les applications ABAP via l’API Microsoft Graph. Dans cet article, nous montrons la configuration complète depuis la configuration Azure jusqu’au code ABAP fonctionnel.
Vue d’ensemble de l’architecture
┌──────────────────────────────────────────────────────────────────────────┐│ Architecture d'intégration Office 365 Mail │├──────────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ││ │ BTP ABAP │ │ Microsoft │ │ Office 365 │ ││ │ Environment │────>│ Entra ID │────>│ Mail Server │ ││ │ │ │ (Azure AD) │ │ │ ││ └─────────────────┘ └─────────────────┘ └─────────────────┘ ││ │ │ ││ │ │ ││ ▼ ▼ ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ Communication │ │ OAuth 2.0 │ ││ │ Arrangement │ │ Access Token │ ││ └─────────────────┘ └─────────────────┘ ││ ││ Protocole : HTTPS + OAuth 2.0 Client Credentials Flow ││ API : Microsoft Graph API (https://graph.microsoft.com) ││ │└──────────────────────────────────────────────────────────────────────────┘Prérequis
| Composant | Exigence |
|---|---|
| Azure AD | Tenant avec droits Global Admin ou Application Admin |
| Office 365 | Licence avec Exchange Online (Business Basic ou supérieur) |
| BTP | ABAP Environment avec accès ADT |
| Mailbox | Boîte partagée ou compte de service pour l’envoi |
1. Inscription d’application Azure AD
Tout d’abord, une inscription d’application doit être créée dans Microsoft Entra ID (anciennement Azure AD).
Étape 1 : Enregistrer l’application
1. Ouvrir Azure Portal (portal.azure.com)2. Microsoft Entra ID → App registrations → New registration
Nom : SAP-BTP-Mail-IntegrationTypes de comptes supportés : Accounts in this organizational directory onlyURI de redirection : (laisser vide)
3. Cliquer sur "Register"Étape 2 : Créer un Client Secret
1. Ouvrir App → Certificates & secrets2. Client secrets → New client secret
Description : SAP BTP ABAP EnvironmentExpire : 24 mois (recommandé)
3. Cliquer sur "Add"4. Copier Secret Value IMMÉDIATEMENT et le conserver en sécurité ! (Affiché une seule fois)Étape 3 : Configurer les permissions API
Pour l’envoi de mail via Microsoft Graph, nous avons besoin d’Application Permissions :
1. API permissions → Add a permission2. Microsoft Graph → Application permissions3. Ajouter les permissions suivantes :
☑ Mail.Send (Envoyer des e-mails) ☑ Mail.ReadWrite (optionnel : Créer des brouillons) ☑ User.Read.All (optionnel : Récupérer des informations utilisateur)
4. Cliquer sur "Add permissions"5. Cliquer sur "Grant admin consent for [Tenant]"Important : Les Application Permissions nécessitent un Admin Consent et permettent l’envoi en tant que n’importe quel utilisateur du tenant.
Étape 4 : Noter les IDs importants
Après l’inscription, vous avez besoin des valeurs suivantes :
| Valeur | Où la trouver |
|---|---|
| Application (Client) ID | Page de présentation de l’App |
| Directory (Tenant) ID | Page de présentation de l’App |
| Client Secret | Certificates & secrets (copié lors de la création) |
Exemple :
Application (Client) ID : a1b2c3d4-e5f6-7890-abcd-ef1234567890Directory (Tenant) ID : 12345678-90ab-cdef-1234-567890abcdefClient Secret : Xyz123~AbCdEfGhIjKlMnOpQrStUvWx.YZ2. Créer un Communication Scenario dans ABAP
Créez un Communication Scenario personnalisé pour l’intégration Microsoft Graph.
Définition du Scenario
Dans ADT : Clic droit sur Package → New → Other ABAP Repository Object → Communication Scenario
<?xml version="1.0" encoding="utf-8"?><communicationScenario id="Z_MS_GRAPH_MAIL" scenarioType="customer"> <description>Microsoft Graph API - Mail Integration</description>
<outboundServices> <service id="Z_MS_GRAPH_API"> <description>Microsoft Graph REST API</description> <serviceType>http</serviceType> </service> </outboundServices>
<supportedAuthenticationMethods> <method>oauth2_client_credentials</method> </supportedAuthenticationMethods></communicationScenario>Après activation, le Scenario est disponible pour la configuration administrateur.
3. Configurer Communication Arrangement
La configuration se fait dans les applications Fiori de l’ABAP Environment.
Créer un Communication System
Fiori App : "Maintain Communication Systems"
System ID : MS_GRAPH_PRODNom du système : Microsoft Graph API
Général :├── Nom d'hôte : graph.microsoft.com├── Port : 443└── HTTPS : ☑
Paramètres OAuth 2.0 :├── Type Token Service URL : Dedicated├── Token Service URL : https://login.microsoftonline.com/{Tenant-ID}/oauth2/v2.0/token└── (Remplacer {Tenant-ID} par votre Directory/Tenant ID)Créer un Communication Arrangement
Fiori App : "Maintain Communication Arrangements"
Arrangement ID : Z_MS_GRAPH_MAIL_PRODScenario : Z_MS_GRAPH_MAILCommunication System : MS_GRAPH_PROD
Outbound Services :├── Z_MS_GRAPH_API : Active└── Path : /v1.0
Méthode d'authentification : OAuth 2.0 Client Credentials├── Client ID : [Application (Client) ID depuis Azure]├── Client Secret : [Secret depuis Azure]└── Token Service Scope : https://graph.microsoft.com/.defaultImportant : Le Scope https://graph.microsoft.com/.default est requis pour les Application Permissions.
4. Implémentation ABAP
Maintenant, nous implémentons la classe pour l’envoi d’e-mails via Microsoft Graph.
Classe d’envoi d’e-mail
CLASS zcl_ms_graph_mail DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_email_address, address TYPE string, name TYPE string, END OF ty_email_address, tt_email_addresses TYPE STANDARD TABLE OF ty_email_address WITH EMPTY KEY.
TYPES: BEGIN OF ty_attachment, name TYPE string, content_type TYPE string, content TYPE xstring, END OF ty_attachment, tt_attachments TYPE STANDARD TABLE OF ty_attachment WITH EMPTY KEY.
METHODS send_mail IMPORTING iv_sender_mail TYPE string iv_subject TYPE string iv_body TYPE string iv_is_html TYPE abap_bool DEFAULT abap_false it_to TYPE tt_email_addresses it_cc TYPE tt_email_addresses OPTIONAL it_bcc TYPE tt_email_addresses OPTIONAL it_attachments TYPE tt_attachments OPTIONAL RAISING cx_http_dest_provider_error cx_web_http_client_error.
PRIVATE SECTION. CONSTANTS: c_scenario TYPE if_com_scenario_factory=>ty_cscn_id VALUE 'Z_MS_GRAPH_MAIL', c_service_id TYPE if_com_scenario_factory=>ty_cscn_outb_srv_id VALUE 'Z_MS_GRAPH_API'.
METHODS get_destination RETURNING VALUE(ro_destination) TYPE REF TO if_http_destination RAISING cx_http_dest_provider_error.
METHODS build_mail_json IMPORTING iv_subject TYPE string iv_body TYPE string iv_is_html TYPE abap_bool it_to TYPE tt_email_addresses it_cc TYPE tt_email_addresses it_bcc TYPE tt_email_addresses it_attachments TYPE tt_attachments RETURNING VALUE(rv_json) TYPE string.
METHODS build_recipients_json IMPORTING it_addresses TYPE tt_email_addresses RETURNING VALUE(rv_json) TYPE string.
METHODS build_attachments_json IMPORTING it_attachments TYPE tt_attachments RETURNING VALUE(rv_json) TYPE string.ENDCLASS.
CLASS zcl_ms_graph_mail IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " Envoyer un e-mail de test TRY. send_mail( iv_subject = 'Test depuis SAP BTP" iv_body = |<html><body>| && |<h1>Bienvenue</h1>| && |<p>Ceci est un e-mail de test depuis SAP BTP ABAP Environment.</p>| && |<p>Envoyé : { cl_abap_context_info=>get_system_date( ) }</p>| && |</body></html>| iv_is_html = abap_true name = 'Jean Dupont' ) ) ).
out->write( 'E-mail envoyé avec succès !' ).
CATCH cx_http_dest_provider_error INTO DATA(lx_dest). out->write( |Erreur Destination : { lx_dest->get_text( ) }| ). CATCH cx_web_http_client_error INTO DATA(lx_http). out->write( |Erreur HTTP : { lx_http->get_text( ) }| ). ENDTRY. ENDMETHOD.
METHOD get_destination. ro_destination = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = c_scenario service_id = c_service_id ). ENDMETHOD.
METHOD send_mail. " 1. Récupérer la destination DATA(lo_destination) = get_destination( ).
" 2. Créer le client HTTP DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
TRY. " 3. Configurer la requête DATA(lo_request) = lo_client->get_http_request( ).
" Graph API Endpoint : /users/{user-id}/sendMail lo_request->set_uri_path( |/users/{ iv_sender_mail }/sendMail| ).
" 4. Créer le corps JSON DATA(lv_json) = build_mail_json( iv_subject = iv_subject iv_body = iv_body iv_is_html = iv_is_html it_to = it_to it_cc = it_cc it_bcc = it_bcc it_attachments = it_attachments ).
lo_request->set_text( lv_json ). lo_request->set_header_field( i_name = 'Content-Type" i_value = 'application/json' ).
" 5. Exécuter POST DATA(lo_response) = lo_client->execute( if_web_http_client=>post ). DATA(lv_status) = lo_response->get_status( )-code.
" 202 Accepted = Succès IF lv_status <> 202. DATA(lv_error) = lo_response->get_text( ). RAISE EXCEPTION TYPE cx_web_http_client_error EXPORTING text = |Échec de l'envoi de mail : { lv_status } - { lv_error }|. ENDIF.
CLEANUP. lo_client->close( ). ENDTRY. ENDMETHOD.
METHOD build_mail_json. " Créer la structure JSON Microsoft Graph Mail DATA(lv_content_type) = COND string( WHEN iv_is_html = abap_true THEN 'HTML" ELSE 'Text' ).
rv_json = |\{| && | "message": \{| && | "subject": "{ escape( val = iv_subject format = cl_abap_format=>e_json_string ) }",| && | "body": \{| && | "contentType": "{ lv_content_type }",| && | "content": "{ escape( val = iv_body format = cl_abap_format=>e_json_string ) }"| && | \},| && | "toRecipients": { build_recipients_json( it_to ) }|.
" Ajouter CC (si présent) IF it_cc IS NOT INITIAL. rv_json = rv_json && |,| && | "ccRecipients": { build_recipients_json( it_cc ) }|. ENDIF.
" Ajouter BCC (si présent) IF it_bcc IS NOT INITIAL. rv_json = rv_json && |,| && | "bccRecipients": { build_recipients_json( it_bcc ) }|. ENDIF.
" Ajouter les pièces jointes (si présentes) IF it_attachments IS NOT INITIAL. rv_json = rv_json && |,| && | "attachments": { build_attachments_json( it_attachments ) }|. ENDIF.
rv_json = rv_json && | \},| && | "saveToSentItems": "true"| && |\}|. ENDMETHOD.
METHOD build_recipients_json. DATA: lt_json TYPE TABLE OF string.
LOOP AT it_addresses INTO DATA(ls_address). DATA(lv_entry) = |\{| && | "emailAddress": \{| && | "address": "{ ls_address-address }"|.
IF ls_address-name IS NOT INITIAL. lv_entry = lv_entry && |,| && | "name": "{ escape( val = ls_address-name format = cl_abap_format=>e_json_string ) }"|. ENDIF.
lv_entry = lv_entry && | \}| && |\}|.
APPEND lv_entry TO lt_json. ENDLOOP.
rv_json = |[{ concat_lines_of( table = lt_json sep = `, ` ) }]|. ENDMETHOD.
METHOD build_attachments_json. DATA: lt_json TYPE TABLE OF string.
LOOP AT it_attachments INTO DATA(ls_attachment). " Encodage Base64 du contenu DATA(lv_base64) = cl_web_http_utility=>encode_x_base64( ls_attachment-content ).
DATA(lv_entry) = |\{| && | "@odata.type": "#microsoft.graph.fileAttachment",| && | "name": "{ escape( val = ls_attachment-name format = cl_abap_format=>e_json_string ) }",| && | "contentType": "{ ls_attachment-content_type }",| && | "contentBytes": "{ lv_base64 }"| && |\}|.
APPEND lv_entry TO lt_json. ENDLOOP.
rv_json = |[{ concat_lines_of( table = lt_json sep = `, ` ) }]|. ENDMETHOD.ENDCLASS.5. Exemples pratiques
E-mail texte simple
DATA(lo_mail) = NEW zcl_ms_graph_mail( ).
lo_mail->send_mail( iv_subject = 'Commande confirmée" iv_body = |Cher client,\n\n| && |Votre commande a été enregistrée avec succès.\n\n| && |Cordialement\n| && |Votre système SAP| name = 'Jean Dupont' ) ) ).E-mail HTML avec formatage
DATA(lo_mail) = NEW zcl_ms_graph_mail( ).
DATA(lv_html) = |<html>| && |<head>| && | <style>| && | body \{ font-family: Arial, sans-serif; \}| && | h1 \{ color: #0070c0; \}| && | .highlight \{ background-color: #fff3cd; padding: 10px; \}| && | </style>| && |</head>| && |<body>| && | <h1>Confirmation de réservation</h1>| && | <p>Cher Monsieur Dupont,</p>| && | <p>Votre réservation de vol a été créée avec succès :</p>| && | <div class="highlight">| && | <strong>Numéro de réservation :</strong> FB-2026-001234<br>| && | <strong>Vol :</strong> LH 100<br>| && | <strong>Itinéraire :</strong> Francfort → New York<br>| && | <strong>Date :</strong> 15.03.2026| && | </div>| && | <p>Cordialement,<br>Votre agence de voyage</p>| && |</body>| && |</html>|.
lo_mail->send_mail( iv_subject = 'Votre confirmation de réservation FB-2026-001234" iv_body = lv_html iv_is_html = abap_true name = 'Jean Dupont' ) )E-mail avec pièces jointes
DATA(lo_mail) = NEW zcl_ms_graph_mail( ).
" Données CSV comme pièce jointeDATA(lv_csv) = |Numéro réservation;Client;Montant\n| && |FB-001;Müller;1250.00\n| && |FB-002;Schmidt;890.50\n| && |FB-003;Weber;2100.00|.
" Convertir String en XStringDATA(lv_csv_xstring) = cl_abap_codepage=>convert_to( source = lv_csv codepage = 'UTF-8' ).
" Contenu PDF (par ex. depuis RAP Action ou Adobe Forms)DATA: lv_pdf_xstring TYPE xstring." ... générer PDF ...
lo_mail->send_mail( iv_subject = 'Rapport mensuel des réservations" iv_body = |<html><body>| && |<p>Veuillez trouver ci-joint le rapport des réservations pour février 2026.</p>| && |</body></html>| iv_is_html = abap_true name = 'Direction' ) ) it_attachments = VALUE #( ( name = 'reservations_2026-02.csv" content_type = 'text/csv" content = lv_csv_xstring ) ( name = 'rapport_2026-02.pdf" content_type = 'application/pdf" content = lv_pdf_xstring ) ) ).Envoyer un e-mail depuis RAP Action
METHOD send_booking_confirmation. " RAP Action : Envoyer confirmation de réservation par e-mail
READ ENTITIES OF zi_flightbook IN LOCAL MODE ENTITY FlightBook ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_bookings).
LOOP AT lt_bookings INTO DATA(ls_booking). " Déterminer l'adresse e-mail du client SELECT SINGLE email FROM zcustomer WHERE customer_id = @ls_booking-CustomerId INTO @DATA(lv_customer_email).
IF sy-subrc = 0 AND lv_customer_email IS NOT INITIAL. TRY. DATA(lo_mail) = NEW zcl_ms_graph_mail( ).
DATA(lv_body) = |<html><body>| && |<h2>Confirmation de réservation</h2>| && |<p>Votre réservation a été confirmée :</p>| && |<ul>| && | <li><strong>N° réservation :</strong> { ls_booking-BookingId }</li>| && | <li><strong>Vol :</strong> { ls_booking-FlightNumber }</li>| && | <li><strong>Date :</strong> { ls_booking-FlightDate }</li>| && | <li><strong>Statut :</strong> { ls_booking-Status }</li>| && |</ul>| && |</body></html>|.
lo_mail->send_mail( iv_subject = |Confirmation de réservation { ls_booking-BookingId }| iv_body = lv_body iv_is_html = abap_true it_to = VALUE #( ( address = lv_customer_email ) ) ).
" Message de succès APPEND VALUE #( %tky = ls_booking-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-success text = |E-mail envoyé à { lv_customer_email }| ) ) TO reported-flightbook.
CATCH cx_root INTO DATA(lx_error). " Message d'erreur APPEND VALUE #( %tky = ls_booking-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Erreur mail : { lx_error->get_text( ) }| ) ) TO reported-flightbook. ENDTRY. ENDIF. ENDLOOP.ENDMETHOD.6. Gestion des erreurs
Classe étendue avec gestion des erreurs
CLASS zcl_ms_graph_mail_ext DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_mail_result, success TYPE abap_bool, message TYPE string, error_code TYPE string, END OF ty_mail_result.
METHODS send_mail_safe IMPORTING iv_sender_mail TYPE string iv_subject TYPE string iv_body TYPE string iv_is_html TYPE abap_bool DEFAULT abap_false it_to TYPE zcl_ms_graph_mail=>tt_email_addresses RETURNING VALUE(rs_result) TYPE ty_mail_result.
PRIVATE SECTION. METHODS parse_graph_error IMPORTING iv_json TYPE string RETURNING VALUE(rv_error) TYPE string.ENDCLASS.
CLASS zcl_ms_graph_mail_ext IMPLEMENTATION. METHOD send_mail_safe. TRY. DATA(lo_mail) = NEW zcl_ms_graph_mail( ).
lo_mail->send_mail( iv_sender_mail = iv_sender_mail iv_subject = iv_subject iv_body = iv_body iv_is_html = iv_is_html it_to = it_to ).
rs_result = VALUE #( success = abap_true message = 'E-mail envoyé avec succès' ).
CATCH cx_http_dest_provider_error INTO DATA(lx_dest). rs_result = VALUE #( success = abap_false message = lx_dest->get_text( ) error_code = 'DESTINATION_ERROR' ).
CATCH cx_web_http_client_error INTO DATA(lx_http). rs_result = VALUE #( success = abap_false message = lx_http->get_text( ) error_code = 'HTTP_ERROR' ). ENDTRY. ENDMETHOD.
METHOD parse_graph_error. " Parser le JSON d'erreur Microsoft Graph " Exemple : {"error":{"code":"InvalidAuthenticationToken","message":"..."}} TRY. DATA: BEGIN OF ls_error, BEGIN OF error, code TYPE string, message TYPE string, END OF error, END OF ls_error.
/ui2/cl_json=>deserialize( EXPORTING json = iv_json CHANGING data = ls_error ).
rv_error = |{ ls_error-error-code }: { ls_error-error-message }|.
CATCH cx_root. rv_error = iv_json. ENDTRY. ENDMETHOD.ENDCLASS.Erreurs courantes et solutions
| Code d’erreur | Description | Solution |
|---|---|---|
| 401 Unauthorized | Token invalide ou expiré | Renouveler Client Secret, vérifier Tenant-ID |
| 403 Forbidden | Pas d’autorisation | Accorder Admin Consent, vérifier permission Mail.Send |
| 404 Not Found | Boîte mail expéditeur introuvable | Vérifier l’adresse e-mail de l’expéditeur |
| 400 Bad Request | JSON invalide ou champs manquants | Valider la structure JSON |
| 429 Too Many Requests | Limitation de débit | Implémenter Retry avec Exponential Backoff |
Respecter la limitation de débit
Microsoft Graph a des limites de débit. Pour l’envoi de mail :
Limite : 10 000 requêtes par 10 minutes par App 10 000 e-mails par jour par boîte mail
Bonne pratique :- Éviter l'envoi par lots- En cas de 429 : Respecter l'en-tête Retry-After- Application Logging pour le monitoring7. Boîte partagée vs Compte de service
Pour une utilisation en production, une Boîte partagée est recommandée :
| Aspect | Boîte partagée | Compte de service |
|---|---|---|
| Licence | Pas de licence propre nécessaire | Nécessite licence Exchange |
| Coûts | Gratuit | Coûts mensuels |
| Gestion | Via Exchange Admin Center | Comme utilisateur normal |
| Recommandation | Pour mails automatiques | Seulement si nécessaire |
Configurer une Boîte partagée
1. Microsoft 365 Admin Center → Teams & Groups → Shared Mailboxes2. Cliquer sur "Add a shared mailbox"3. Nom : SAP-Notifications Email : [email protected]4. Créer la boîte mail5. Dans Azure AD : Permission App Mail.Send autorise l'envoi via cette boîte8. Bonnes pratiques de sécurité
| Sujet | Recommandation |
|---|---|
| Rotation Secret | Renouveler Client Secret tous les 6-12 mois |
| Least Privilege | Seulement permission Mail.Send, pas de droits de lecture |
| Sender-Whitelist | Autoriser uniquement des boîtes mail dédiées comme expéditeurs |
| Logging | Enregistrer toutes les actions mail avec Application Logging |
| Monitoring | Surveiller les Azure AD Sign-in Logs |
| Error Handling | Ne pas afficher de données sensibles dans les messages d’erreur |
Intégrer Application Logging
METHOD send_mail_with_logging. " Initialiser le logging DATA(lo_log) = cl_bali_log=>create_with_header( header = cl_bali_header_setter=>create( )->set_object( 'ZMS_GRAPH' ) ->set_subobject( 'MAIL' ) ).
TRY. DATA(lo_mail) = NEW zcl_ms_graph_mail( ). lo_mail->send_mail( iv_sender_mail = iv_sender iv_subject = iv_subject iv_body = iv_body it_to = it_recipients ).
" Logger le succès lo_log->add_item( cl_bali_message_setter=>create( severity = if_bali_constants=>c_severity_status id = 'ZMS_GRAPH" number = '001' )->set_text( |Mail envoyé à { it_recipients[ 1 ]-address }| ) ).
CATCH cx_root INTO DATA(lx_error). " Logger l'erreur lo_log->add_item( cl_bali_message_setter=>create( severity = if_bali_constants=>c_severity_error id = 'ZMS_GRAPH" number = '002' )->set_text( |Erreur : { lx_error->get_text( ) }| ) ). ENDTRY.
" Enregistrer le log cl_bali_log_db=>get_instance( )->save_log( lo_log ).ENDMETHOD.Conclusion
L’intégration d’Office 365 Mail dans SAP BTP ABAP Environment offre une alternative moderne aux solutions de messagerie classiques. La combinaison de l’API Microsoft Graph, OAuth 2.0 et du Communication Management dans ABAP Cloud permet une fonctionnalité e-mail sécurisée et facilement maintenable.
Points importants :
- Inscription d’application Azure AD avec Application Permissions (Mail.Send)
- Communication Scenario pour une séparation claire entre développement et configuration
- OAuth 2.0 Client Credentials pour l’authentification Machine-to-Machine
- Boîte partagée comme solution d’expéditeur rentable
- Gestion des erreurs et logging pour l’exploitation en production
Articles complémentaires
- Communication Scenarios Guide - Guide complet sur Communication Management
- HTTP Client in ABAP - Appeler des API REST
- Application Logging - Logging dans ABAP Cloud
- OAuth JWT in ABAP Cloud - Authentification basée sur les tokens