Technical Articles
“View in Browser” functionality for SAP Marketing
With the 1708 release an API and reusable blocks which can be used to build a customer managed “View in Browser” functionality in the Cloud and in on-Premise has been implemented. Please follow this blog entry for more details.
SAP Marketing allows to design and send highly personalized and dynamic emails, as Jan describes in his blog entry. Unfortunately, some email clients and mobile devices don’t support all HTML and/or CSS style elements such as CSS positioning or external stylesheets. Some email clients even block all images in emails by default.
These issues can get resolved by including a “view in browser” link to a browser-friendly version of the email. When recipients click the “view in browser” link, it opens a web page in their browser that displays the online version of the email. This way, any recipients whose email client or device doesn’t support all elements can still see the email the way it was intended to be seen.
Depending on the template, one of the following links can be added to the header or (less usual) in the footer of the email content:
- View this email in your browser
- Email not displaying correctly? View it in your browser.
The setup of the “view in browser” link within the Content Studio is shown below:
Once a recipient clicked on the “view in browser” link, SAP Marketing will automatically fetch the personalized email and open a web page in their browser that displays the online version of the email. The overall process in summarized below:
In more detail, the script (in this context a PHP script was used) on the web server is being called using the outbound ID from the personalized “view in browser” link. The “view in browser” OData service is then being called by the script using the same outbound ID.
The responsibility of the OData service is to return the personalized mail for a given outbound ID. The outbound ID can be used to uniquely identify both the personalization attributes (e.g. first name = Tim) for the recipient and the email content (e.g. Hybris FC mail) which was used by the email campaign. An exemplary logic which can be used to determine the email content (or more specifically: the email content ID) by the outbound ID is shown below in method GET_CONTENT_ID.
* +-------------------------------------------------------------------------------------------------+
* | Method GET_CONTENT_ID
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_EXEC_HASH TYPE /BOBF/CONF_KEY
* | [<-()] RV_CONTENT_ID TYPE CUAN_ME_ENGAGEMENT_ID
* +-------------------------------------------------------------------------------------------------+
METHOD GET_CONTENT_ID.
DATA:
lt_key TYPE /bobf/t_frw_key,
lt_action_parameter TYPE cuan_t_marketing_orc_act_par.
DATA(lo_srv_mktorc) = /bobf/cl_tra_serv_mgr_factory=>get_service_manager( iv_bo_key = if_cuan_marketing_orch_c=>sc_bo_key ).
APPEND VALUE #( key = iv_exec_hash ) TO lt_key.
lo_srv_mktorc->retrieve_by_association(
EXPORTING
iv_node_key = if_cuan_marketing_orch_c=>sc_node-execution_run
iv_association = if_cuan_marketing_orch_c=>sc_association-execution_run-to_parent
it_key = lt_key
IMPORTING
et_target_key = DATA(lt_action_key) ).
lo_srv_mktorc->retrieve_by_association(
EXPORTING
iv_node_key = if_cuan_marketing_orch_c=>sc_node-action
iv_association = if_cuan_marketing_orch_c=>sc_association-action-action_parameter
it_key = lt_action_key
iv_fill_data = abap_true
IMPORTING
et_data = lt_action_parameter ).
* read content ID from action parameter
READ TABLE lt_action_parameter ASSIGNING FIELD-SYMBOL(<ls_action_parameter>)
WITH KEY parameter_id = if_cuan_mkt_orch_constants=>sc_action_parameter_id-email_template_id.
IF sy-subrc IS INITIAL.
rv_content_id = <ls_action_parameter>-parameter_value.
ENDIF.
ENDMETHOD.
In the next step, the personalization attributes have to be determined using the outbound ID. The personalization attributes are persisted in the table CUAND_EXEC_HASH (UTF-8 encoded as JSON structure in the PLACEHOLDER_VALUES field). Combining both the personalization information and the email content, the personalized mail can be re-built. The exemplary method GET_HTML_CONTENT is shown below:
* +-------------------------------------------------------------------------------------------------+
* | GET_HTML_CONTENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_EXEC_HASH TYPE CUAN_MKT_EXEC_HASH
* | [--->] IV_CONTENT_ID TYPE CUAN_ME_ENGAGEMENT_ID
* | [--->] IT_PLACEHOLDERS TYPE CUAN_T_ME_DYNAMIC_CONTENT
* | [<-()] RV_HTML TYPE STRING
* +-------------------------------------------------------------------------------------------------+
METHOD get_html_content.
DATA lo_email_handler TYPE REF TO cl_cuan_mkt_exec_email.
DATA lt_parameters TYPE cuan_t_mkt_exec_param.
DATA lr_dynamic_content TYPE REF TO cuan_t_me_dynamic_content.
DATA lt_outbounds TYPE cuan_t_mkt_exec_pers_content.
DATA ls_outbound TYPE cuan_s_mkt_exec_pers_content.
DATA lv_path TYPE string.
TRY.
CREATE OBJECT lo_email_handler TYPE cl_cuan_mkt_exec_email_amzn EXPORTING it_parameters = lt_parameters.
CATCH cx_root.
MESSAGE e012(cuan_mkt_exec_frw) INTO DATA(lv_message) WITH 'CL_CUAN_MKT_EXEC_EMAIL_AMZN'.
RETURN.
ENDTRY.
CREATE DATA lr_dynamic_content.
lr_dynamic_content->* = it_placeholders.
ls_outbound-dynamic_content = lr_dynamic_content.
ls_outbound-email_message_ref = lo_email_handler.
ls_outbound-personalization_hash = iv_exec_hash.
APPEND ls_outbound TO lt_outbounds.
lv_path = me->get_tracking_path( ).
cl_cuan_a_me_personalize=>convert_message( EXPORTING iv_campaign_content_id = iv_content_id
iv_host = lv_path
CHANGING ct_contacts = lt_outbounds ).
rv_html = lo_email_handler->if_cuan_mkt_exec_email~get_body_html( ).
ENDMETHOD.
The overall process is shown in the picture below:
Once the personalized email has been re-built, it is returned from the OData service and handed over 1:1 by the service in order to display it in the web browser. Additionally, the service on the customer webserver can be adapted in a way that is displays content or redirects to a specific URL as a fallback option when the backend is not reachable.
This functionality has been built as a custom solution on top of SAP Marketing. In case of any questions, don’t hesitate to contact me.
Hi Tim,
thanks for sharing!
Cheers
Jan
Hello @Tim,
Thanks for sharing , i have couple of questions:
1. which OData service is been used?
2. where to get the "fallback URL"?
Regards,
Krish
Hi @Krish,
I have updated the blog post and added some coding examples. In context of this custom solution, a new OData service has been developed. The URL has to point to the service on the customer webservice.
In case of additional questions, don't hesitate to contact me.
Regards,
Tim
Thanks, very useful blog post!
Maris
Thank you, this is very interesting!
I think SAP should include this in the roadmap for hybris marketing to build it into the solution. This is a functionality that almost all other marketing automation tools offer.
Regards
Joyca
Great blog, Tim! Thank you.
Hi Tim,
Thanks for this blog.
I followed your blog and created a ODATA service ( using SAP gateway service builder).
The entity type is having two parameters i.e. “OutboundID’ ( key) and “EmailHtml”.
I have added following URL in email.
http://XXXXX.com:8040/sap/opu/odata/sap/zmkt_email_odata_srv/EMAIL_ODATASet
Also redefined the “GET_ENTITYSET” method and added logic to get the email content ID and related email.
However the problem I am facing is the following –
Following is the metadata –
<edmx:Edmx Version="1.0"><edmx:DataServices m:DataServiceVersion="2.0"><Schema Namespace="ZMKT_EMAIL_ODATA_SRV" xml:lang="en" sap:schema-version="1"><EntityType Name="EMAIL_ODATA" sap:content-version="1"><Key><PropertyRef Name="OutboundId"/></Key><Property Name="OutboundId" Type="Edm.Binary" Nullable="false" sap:label="NodeID" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/><Property Name="EmailHtml" Type="Edm.String" Nullable="false" sap:label="EMAIL_HTML" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/></EntityType><EntityContainer Name="ZMKT_EMAIL_ODATA_SRV_Entities" m:IsDefaultEntityContainer="true" sap:supported-formats="atom json xlsx"><EntitySet Name="EMAIL_ODATASet" EntityType="ZMKT_EMAIL_ODATA_SRV.EMAIL_ODATA" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:pageable="false" sap:content-version="1"/></EntityContainer><atom:link rel="self" href="http://XXXXXX/sap/opu/odata/sap/zmkt_email_odata_srv/$metadata"/><atom:link rel="latest-version" href="http://XXXXXXX/sap/opu/odata/sap/zmkt_email_odata_srv/$metadata"/></Schema></edmx:DataServices></edmx:Edmx>
Following code in “GET_ENTITYSET” –
METHOD email_odataset_get_entityset.
DATA: ls_entityset TYPE zcl_zmkt_email_odata_mpc=>ts_email_odata,
lv_outbound_id TYPE /bobf/conf_key,
it_source_key TYPE /iwbep/t_mgw_tech_pairs,
lv_content_id TYPE cuan_me_engagement_id,
lv_email TYPE string.
FIELD-SYMBOLS: <ls_key_outbound_id> TYPE /iwbep/s_mgw_name_value_pair.
FIELD-SYMBOLS: <ls_source_key> TYPE /iwbep/s_mgw_tech_pair.
CONSTANTS : co_outbound_id TYPE string VALUE 'OUTBOUND_ID'.
* * find Outbound key
READ TABLE it_key_tab ASSIGNING <ls_key_outbound_id> WITH KEY name = co_outbound_id.
IF sy-subrc = 0.
lv_outbound_id = <ls_key_outbound_id>-value.
ELSE.
it_source_key = io_tech_request_context->get_source_keys( ).
READ TABLE it_source_key WITH KEY name = 'OUTBOUND_ID' ASSIGNING <ls_source_key>.
IF sy-subrc = 0.
lv_outbound_id = <ls_key_outbound_id>-value.
ENDIF.
ENDIF.
*Get content ID
IF lv_outbound_id IS NOT INITIAL.
lv_content_id = me->get_content_id( EXPORTING iv_exec_hash = lv_outbound_id ).
*Get email
lv_email = me->get_html_content( EXPORTING iv_content_id = lv_content_id ).
ENDIF.
*Update entity set
IF lv_email IS NOT INITIAL.
ls_entityset-email_html = lv_email.
ls_entityset-outbound_id = lv_outbound_id.
APPEND ls_entityset TO et_entityset.
ENDIF.
ENDMETHOD.
Thanks,
Debashis
Hi Debashis,
EMAIL_ODATASet(‘BE32914DBEKD’), BE32914DBEKD being the outbound id.
Within GET_ENTITY you can then access the URL parameters like this:
CL_CUAN_MKT_EXEC_EXECUTE_EMAIL->GET_TRACKING_PATH. I needed to do some customer-specific logic, that is why I added an own GET_TRACKING_PATH method to the class.
I hope this helps!
Best regards,
Tim
Hi Tim Nusch,
We are not able to get the logic to pass iv_campaign_content_id from PERSONALIZATION HASH?
As you mention UTF-8 encoded as JSON structure in the PLACEHOLDER_VALUES field. Combining both the personalization information and the email content, the personalized mail can be re-built.
How to decode Email Content id from placeholder value that is only road block as of now. In the case of multiple emails in a campaign, it will be difficlut to pass email content ID
Vaibhav,
You can decode the placeholder_values to a json message like this
cl_abap_conv_in_ce=>create( input = l_rawhtml
encoding = 'UTF-8'
replacement = '?'
ignore_cerr = abap_false ).
after that i don't know yet how to dynamically transform the json to a table of cuan_t_me_dynamic_content that will be used as a parameter of the get_content_html
Thanks, Denis Galand,
We are able to figure out from Table CUAND_EXEC_HASH -Field EXEC_RUN_KEY
After passing EXEC_RUN_KEY value in GET_CONTENT_ID we are able to get Content ID.
Good, now i'm still blocked on how to fill the
lt_placeholders TYPE cuan_t_me_dynamic_content
is there a generic way to convert the json to the placeholders?
Hi Denis,
in context of this custom solution a custom logic/parser to convert the JSON into the internal table structure had been developed because no generic way was available.
I am not allowed to publish the whole logic here because of the given conditions of a custom solution.
Maybe this regex helps to bring you a few steps forward:
This regex matches multiple key-value pairs in the given JSON. Two subgroups (one for the key and one for the value) are created for each match. You can then create the internal table structure using the regex matches.
I hope this helps!
Best regards,
Tim
Thanks Tim, that helped to transform the json to variables
But the most difficult was to know how to compute the param and values to make them usable in the convert message method which substitute the dynamic content, i will put an example here for the others if they want to know directly
The JSON
{
}
}
},]
}
Data ls_placeholders LIKE LINE OF lt_placeholders.
ls_placeholders-ds_attribute_name = BO-CUAN_INTERACTION_CONTACT-NAME_FIRST
ls_placeholders-ds_attribute_value = "denis@galand.be"
Hi Tim,
Can you share the Javascript for Web page?. That is the only missing part we have as of now.
Hi Denis,
I used following logic to fill placeholder table. And convert it dynamically , this worked.
Hints for string operation - the parameter and the respective value followed below pattern i.e. separated by : and available inside double quote “”.
“DATE_OF_BIRTH“:“19840925”
Thanks,
Debashis
Hi Vaibhav,
I am not allowed to share all logic for PHP, however following hints may help you.
Thanks,
Debashis
Hi Debashis,
could you evaluate this part of code. We are not able to receive a valid response but if we try the service as http request via browser works propery.
Are you able to find an error in this call using cURL command?
Regards, Roberto
Hi Roberto,
I will build the URL in following ways.
phpif( isset($_GET[‘sap-outbound-id’]) && !empty( $_GET[‘sap-outbound-id’] ) )
{
$campaignId = $_GET[‘sap-outbound-id’];
$url = "http://server:8010/sap/opu/odata/SAP/ZMKTPRO_SEARCH_BP_SRV/BUPACollectionSet?sap-outbound-id=".$campaignId;
….
}
Thanks,
Debashis
Hi Tim,
Great blog! Conversely, is there a way view the email as text?
Thanks!
Taylor Barsamian
Hi Taylor,
that requirement is not included in the custom solution and has to be built separately or on top of it.
Best regards,
Tim
With the 1708 release an API and reusable blocks which can be used to build a customer managed “View in Browser” functionality in the Cloud and in on-Premise has been implemented. Please follow this blog entry for more details.
Hi Tim
Would you check below links ??
Even though it has all data in CUAND_CE_IA_RT and CUAND_EXEC_HASH, one is working but another is not.
(WORKING)
https://www.global-cdm.net:8080/VIEWINBROWSER/?_L54AD1F204_=c2NlbmFyaW89U0VCJnRlbmFudD1DU1AxMDAmQ2FtcGFpZ25PdXRib3VuZD0nNjQwQzBGNjVDRDc2RTMwQTk4NzVGNTFDOUIyMEM1RjYzMkExRThCRScmTGlua1RyYWNraW5nSXNEaXNhYmxlZD1mYWxzZQ
(404 ERROR)
https://www.global-cdm.net:8080/VIEWINBROWSER/?_L54AD1F204_=c2NlbmFyaW89U0VCJnRlbmFudD1DU1AxMDAmQ2FtcGFpZ25PdXRib3VuZD0nMTBEMjY1NzY1MDgzMTc2RDdCMUUyNDM2QjdGQzI4OUE4QkVEODNDOCcmTGlua1RyYWNraW5nSXNEaXNhYmxlZD1mYWxzZQ
Regards,
SJ from GCDM 🙂