Integracion de Office 365 Mail en BTP: Enviar correos via Microsoft Graph API

Kategorie
Integration
Veröffentlicht
Autor
Johannes

La integracion de Office 365 E-Mail en SAP BTP ABAP Environment permite el envio de correos electronicos directamente desde aplicaciones ABAP a traves de la Microsoft Graph API. En este articulo mostramos la configuracion completa desde la configuracion en Azure hasta el codigo ABAP funcional.

Vision general de la arquitectura

+--------------------------------------------------------------------------+
| Arquitectura de integracion Office 365 Mail |
+--------------------------------------------------------------------------+
| |
| +-----------------+ +-----------------+ +-----------------+ |
| | BTP ABAP | | Microsoft | | Office 365 | |
| | Environment |---->| Entra ID |---->| Mail Server | |
| | | | (Azure AD) | | | |
| +-----------------+ +-----------------+ +-----------------+ |
| | | |
| | | |
| v v |
| +-----------------+ +-----------------+ |
| | Communication | | OAuth 2.0 | |
| | Arrangement | | Access Token | |
| +-----------------+ +-----------------+ |
| |
| Protocolo: HTTPS + OAuth 2.0 Client Credentials Flow |
| API: Microsoft Graph API (https://graph.microsoft.com) |
| |
+--------------------------------------------------------------------------+

Requisitos previos

ComponenteRequisito
Azure ADTenant con permisos Global Admin o Application Admin
Office 365Licencia con Exchange Online (Business Basic o superior)
BTPABAP Environment con acceso ADT
MailboxShared Mailbox o Service Account para el envio

1. Registro de App en Azure AD

Primero se debe crear un registro de app en Microsoft Entra ID (anteriormente Azure AD).

Paso 1: Registrar App

1. Abrir Azure Portal (portal.azure.com)
2. Microsoft Entra ID -> App registrations -> New registration
Name: SAP-BTP-Mail-Integration
Supported account types: Accounts in this organizational directory only
Redirect URI: (dejar vacio)
3. Click en "Register"

Paso 2: Crear Client Secret

1. Abrir App -> Certificates & secrets
2. Client secrets -> New client secret
Description: SAP BTP ABAP Environment
Expires: 24 months (recomendado)
3. Click en "Add"
4. Copiar Secret Value INMEDIATAMENTE y guardar de forma segura!
(Solo se muestra una vez)

Paso 3: Configurar permisos API

Para el envio de correos via Microsoft Graph necesitamos Application Permissions:

1. API permissions -> Add a permission
2. Microsoft Graph -> Application permissions
3. Agregar los siguientes permisos:
[x] Mail.Send (Enviar correos)
[x] Mail.ReadWrite (opcional: Crear borradores)
[x] User.Read.All (opcional: Obtener info de usuario)
4. Click en "Add permissions"
5. Click en "Grant admin consent for [Tenant]"

Importante: Application Permissions requieren Admin Consent y permiten enviar como cualquier usuario en el Tenant.

Paso 4: Anotar IDs importantes

Despues del registro necesitas los siguientes valores:

ValorDonde encontrarlo
Application (Client) IDPagina Overview de la App
Directory (Tenant) IDPagina Overview de la App
Client SecretCertificates & secrets (copiado al crear)

Ejemplo:

Application (Client) ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Directory (Tenant) ID: 12345678-90ab-cdef-1234-567890abcdef
Client Secret: Xyz123~AbCdEfGhIjKlMnOpQrStUvWx.YZ

2. Crear Communication Scenario en ABAP

Crea un Communication Scenario personalizado para la integracion con Microsoft Graph.

Definicion del Scenario

En ADT: Click derecho en 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>

Despues de activar, el Scenario esta disponible para la configuracion del administrador.

3. Configurar Communication Arrangement

La configuracion se realiza en las apps Fiori del ABAP Environment.

Crear Communication System

Fiori App: "Maintain Communication Systems"
System ID: MS_GRAPH_PROD
System Name: Microsoft Graph API
General:
+-- Host Name: graph.microsoft.com
+-- Port: 443
+-- HTTPS: [x]
OAuth 2.0 Settings:
+-- Token Service URL Type: Dedicated
+-- Token Service URL: https://login.microsoftonline.com/{Tenant-ID}/oauth2/v2.0/token
+-- (Reemplaza {Tenant-ID} con tu Directory/Tenant ID)

Crear Communication Arrangement

Fiori App: "Maintain Communication Arrangements"
Arrangement ID: Z_MS_GRAPH_MAIL_PROD
Scenario: Z_MS_GRAPH_MAIL
Communication System: MS_GRAPH_PROD
Outbound Services:
+-- Z_MS_GRAPH_API: Active
+-- Path: /v1.0
Authentication Method: OAuth 2.0 Client Credentials
+-- Client ID: [Application (Client) ID de Azure]
+-- Client Secret: [Secret de Azure]
+-- Token Service Scope: https://graph.microsoft.com/.default

Importante: El scope https://graph.microsoft.com/.default es requerido para Application Permissions.

4. Implementacion ABAP

Ahora implementamos la clase para el envio de correos via Microsoft Graph.

Clase de envio de correos

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.
" Enviar correo de prueba
TRY.
send_mail(
iv_sender_mail = '[email protected]'
iv_subject = 'Test desde SAP BTP'
iv_body = |<html><body>| &&
|<h1>Bienvenido</h1>| &&
|<p>Este es un correo de prueba desde SAP BTP ABAP Environment.</p>| &&
|<p>Enviado: { cl_abap_context_info=>get_system_date( ) }</p>| &&
|</body></html>|
iv_is_html = abap_true
it_to = VALUE #( ( address = '[email protected]'
name = 'Juan Perez' ) ) ).
out->write( 'Correo enviado exitosamente!' ).
CATCH cx_http_dest_provider_error INTO DATA(lx_dest).
out->write( |Error de Destination: { lx_dest->get_text( ) }| ).
CATCH cx_web_http_client_error INTO DATA(lx_http).
out->write( |Error 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. Obtener destination
DATA(lo_destination) = get_destination( ).
" 2. Crear cliente HTTP
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination(
i_destination = lo_destination ).
TRY.
" 3. Configurar request
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. Crear JSON body
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. Ejecutar POST
DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
DATA(lv_status) = lo_response->get_status( )-code.
" 202 Accepted = Exitoso
IF lv_status <> 202.
DATA(lv_error) = lo_response->get_text( ).
RAISE EXCEPTION TYPE cx_web_http_client_error
EXPORTING
text = |Envio de correo fallido: { lv_status } - { lv_error }|.
ENDIF.
CLEANUP.
lo_client->close( ).
ENDTRY.
ENDMETHOD.
METHOD build_mail_json.
" Crear estructura JSON de 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 ) }|.
" Agregar CC (si existe)
IF it_cc IS NOT INITIAL.
rv_json = rv_json && |,| &&
| "ccRecipients": { build_recipients_json( it_cc ) }|.
ENDIF.
" Agregar BCC (si existe)
IF it_bcc IS NOT INITIAL.
rv_json = rv_json && |,| &&
| "bccRecipients": { build_recipients_json( it_bcc ) }|.
ENDIF.
" Agregar adjuntos (si existen)
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).
" Codificacion Base64 del contenido
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. Ejemplos practicos

Correo de texto simple

DATA(lo_mail) = NEW zcl_ms_graph_mail( ).
lo_mail->send_mail(
iv_sender_mail = '[email protected]'
iv_subject = 'Pedido confirmado'
iv_body = |Estimado cliente,\n\n| &&
|Su pedido ha sido registrado exitosamente.\n\n| &&
|Saludos cordiales\n| &&
|Su sistema SAP|
it_to = VALUE #( ( address = '[email protected]'
name = 'Juan Perez' ) ) ).

Correo HTML con formato

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>Confirmacion de reserva</h1>| &&
| <p>Estimado Sr. Perez,</p>| &&
| <p>Su reserva de vuelo ha sido creada exitosamente:</p>| &&
| <div class="highlight">| &&
| <strong>Numero de reserva:</strong> FB-2026-001234<br>| &&
| <strong>Vuelo:</strong> LH 100<br>| &&
| <strong>Ruta:</strong> Frankfurt - New York<br>| &&
| <strong>Fecha:</strong> 15.03.2026| &&
| </div>| &&
| <p>Atentamente,<br>Su agencia de viajes</p>| &&
|</body>| &&
|</html>|.
lo_mail->send_mail(
iv_sender_mail = '[email protected]'
iv_subject = 'Su confirmacion de reserva FB-2026-001234'
iv_body = lv_html
iv_is_html = abap_true
it_to = VALUE #( ( address = '[email protected]'
name = 'Juan Perez' ) )
it_cc = VALUE #( ( address = '[email protected]' ) ) ).

Correo con adjuntos

DATA(lo_mail) = NEW zcl_ms_graph_mail( ).
" Datos CSV como adjunto
DATA(lv_csv) = |NumeroReserva;Cliente;Importe\n| &&
|FB-001;Mueller;1250.00\n| &&
|FB-002;Schmidt;890.50\n| &&
|FB-003;Weber;2100.00|.
" Convertir String a XString
DATA(lv_csv_xstring) = cl_abap_codepage=>convert_to(
source = lv_csv
codepage = 'UTF-8' ).
" Contenido PDF (ej. desde RAP Action o Adobe Forms)
DATA: lv_pdf_xstring TYPE xstring.
" ... generar PDF ...
lo_mail->send_mail(
iv_sender_mail = '[email protected]'
iv_subject = 'Informe mensual de reservas'
iv_body = |<html><body>| &&
|<p>Adjunto encontrara el informe de reservas de febrero 2026.</p>| &&
|</body></html>|
iv_is_html = abap_true
it_to = VALUE #( ( address = '[email protected]'
name = 'Direccion' ) )
it_attachments = VALUE #(
( name = 'reservas_2026-02.csv'
content_type = 'text/csv'
content = lv_csv_xstring )
( name = 'informe_2026-02.pdf'
content_type = 'application/pdf'
content = lv_pdf_xstring ) ) ).

Enviar correo desde RAP Action

METHOD send_booking_confirmation.
" RAP Action: Enviar confirmacion de reserva por correo
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).
" Obtener direccion de correo del cliente
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>Confirmacion de reserva</h2>| &&
|<p>Su reserva ha sido confirmada:</p>| &&
|<ul>| &&
| <li><strong>No. reserva:</strong> { ls_booking-BookingId }</li>| &&
| <li><strong>Vuelo:</strong> { ls_booking-FlightNumber }</li>| &&
| <li><strong>Fecha:</strong> { ls_booking-FlightDate }</li>| &&
| <li><strong>Estado:</strong> { ls_booking-Status }</li>| &&
|</ul>| &&
|</body></html>|.
lo_mail->send_mail(
iv_sender_mail = '[email protected]'
iv_subject = |Confirmacion de reserva { ls_booking-BookingId }|
iv_body = lv_body
iv_is_html = abap_true
it_to = VALUE #( ( address = lv_customer_email ) ) ).
" Mensaje de exito
APPEND VALUE #(
%tky = ls_booking-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-success
text = |Correo enviado a { lv_customer_email }| )
) TO reported-flightbook.
CATCH cx_root INTO DATA(lx_error).
" Mensaje de error
APPEND VALUE #(
%tky = ls_booking-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Error de correo: { lx_error->get_text( ) }| )
) TO reported-flightbook.
ENDTRY.
ENDIF.
ENDLOOP.
ENDMETHOD.

6. Manejo de errores

Clase extendida con manejo de errores

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 = 'Correo enviado exitosamente' ).
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.
" Parsear JSON de error de Microsoft Graph
" Ejemplo: {"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.

Errores frecuentes y soluciones

Codigo de errorDescripcionSolucion
401 UnauthorizedToken invalido o expiradoRenovar Client Secret, verificar Tenant-ID
403 ForbiddenSin permisoOtorgar Admin Consent, verificar permiso Mail.Send
404 Not FoundMailbox del remitente no encontradoVerificar direccion de correo del remitente
400 Bad RequestJSON invalido o campos faltantesValidar estructura JSON
429 Too Many RequestsRate LimitingImplementar Retry con Exponential Backoff

Considerar Rate Limiting

Microsoft Graph tiene Rate Limits. Para envio de correos:

Limite: 10,000 solicitudes por 10 minutos por App
10,000 correos por dia por Mailbox
Mejores practicas:
- Evitar envio en batch
- Con 429: Respetar header Retry-After
- Application Logging para monitoreo

7. Shared Mailbox vs. Service Account

Para uso productivo se recomienda un Shared Mailbox:

AspectoShared MailboxService Account
LicenciaNo necesita licencia propiaRequiere licencia Exchange
CostosGratuitoCostos mensuales
AdministracionVia Exchange Admin CenterComo usuario normal
RecomendacionPara correos automaticosSolo si es necesario

Configurar Shared Mailbox

1. Microsoft 365 Admin Center -> Teams & Groups -> Shared Mailboxes
2. Click en "Add a shared mailbox"
3. Name: SAP-Notifications
4. Crear Mailbox
5. En Azure AD: App-Permission Mail.Send permite enviar via esta mailbox

8. Mejores practicas de seguridad

TemaRecomendacion
Rotacion de SecretRenovar Client Secret cada 6-12 meses
Minimo privilegioSolo permiso Mail.Send, sin permisos Read
Whitelist de remitentesSolo permitir mailboxes dedicados como remitentes
LoggingProtocolar todas las acciones de correo con Application Logging
MonitoreoMonitorear Azure AD Sign-in Logs
Manejo de erroresNo exponer datos sensibles en mensajes de error

Integrar Application Logging

METHOD send_mail_with_logging.
" Inicializar 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 ).
" Registrar exito
lo_log->add_item( cl_bali_message_setter=>create(
severity = if_bali_constants=>c_severity_status
id = 'ZMS_GRAPH'
number = '001' )->set_text( |Correo a { it_recipients[ 1 ]-address } enviado| ) ).
CATCH cx_root INTO DATA(lx_error).
" Registrar error
lo_log->add_item( cl_bali_message_setter=>create(
severity = if_bali_constants=>c_severity_error
id = 'ZMS_GRAPH'
number = '002' )->set_text( |Error: { lx_error->get_text( ) }| ) ).
ENDTRY.
" Guardar log
cl_bali_log_db=>get_instance( )->save_log( lo_log ).
ENDMETHOD.

Conclusion

La integracion de Office 365 Mail en SAP BTP ABAP Environment ofrece una alternativa moderna a las soluciones de correo clasicas. La combinacion de Microsoft Graph API, OAuth 2.0 y el Communication Management en ABAP Cloud permite una funcionalidad de correo segura y bien mantenible.

Puntos importantes:

  1. Registro de App en Azure AD con Application Permissions (Mail.Send)
  2. Communication Scenario para separacion limpia entre desarrollo y configuracion
  3. OAuth 2.0 Client Credentials para autenticacion Machine-to-Machine
  4. Shared Mailbox como solucion de remitente economica
  5. Manejo de errores y Logging para operacion productiva

Articulos relacionados