Skip to Content
Technical Articles
Author's profile photo Tim Nusch

“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.

Assigned tags

      23 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jan Matthes
      Jan Matthes

      Hi Tim,

      thanks for sharing!

      Cheers
      Jan
       

      Author's profile photo Krishnendu Laha
      Krishnendu Laha

      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

      Author's profile photo Tim Nusch
      Tim Nusch
      Blog Post Author

      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

      Author's profile photo Maris Sauka
      Maris Sauka

      Thanks, very useful blog post!

      Maris

      Author's profile photo Joyca Vervinckt
      Joyca Vervinckt

      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

      Author's profile photo Former Member
      Former Member

      Great blog, Tim! Thank you.

      Author's profile photo Former Member
      Former Member

      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 –

      1. The table “IT_KEY_TAB” is blank. I am not sure how to pass the outbound ID  to the ODATA service parameter   “OutboundID’. Am I missing anything here?
      2. lv_path = me->get_tracking_path( ). Please let me know what is this path referring, how to get this path. It seems this method is a custom method.

      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

       

      Author's profile photo Tim Nusch
      Tim Nusch
      Blog Post Author

      Hi Debashis,

      1. I used GET_ENTITY instead of GET_ENTITYSET and passed the outbound id like this: http://XXXXX.com:8040/sap/opu/odata/sap/zmkt_email_odata_srv/
        EMAIL_ODATASet(‘BE32914DBEKD’), BE32914DBEKD being the outbound id.
        Within GET_ENTITY you can then access the URL parameters like this:
        lt_parameters = io_tech_request_context->get_keys( ).
        READ TABLE lt_parameters WITH KEY name = 'OUTBOUND_ID' INTO ls_parameters.​
      2. For get_tracking_path I mainly used the logic from

        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

      Author's profile photo Former Member
      Former Member

      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

      Author's profile photo Denis Galand
      Denis Galand

      Vaibhav,

      You can decode the placeholder_values to a json message like this

      cl_abap_conv_in_ce=>createinput       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

      Author's profile photo Former Member
      Former Member

      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.

      Author's profile photo Denis Galand
      Denis Galand

      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?

       

       

      Author's profile photo Tim Nusch
      Tim Nusch
      Blog Post Author

      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:

      (\"\w*\"\:)(\"[^\,\{\}\[\]]*\")

      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

      Author's profile photo Denis Galand
      Denis Galand

      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

      {

      • "TS":[{
        • "T":"BO",
        • "DS":{
          • "D":"CUAN_INTERACTION_CONTACT",
          • "AS":{
            • "DATE_OF_BIRTH":"19840925",
            • "LANGUAGE":"F",
            • "NAME_FIRST":"Denis",
            • "SMTP_ADDR":"denis@galand.be"

            }

          }

        },]

      }

      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"

      Author's profile photo Former Member
      Former Member

      Hi Tim,

      Can you share the Javascript for Web page?. That is the only missing part we have as of now.

      Author's profile photo Former Member
      Former Member

      Hi Denis,

      I used following logic to fill placeholder table. And convert it dynamically , this worked.

       

      1. Populate an internal table with the personalization attribute using below class and method
        1. Class - cl_cuan_cpg_msg_html_util
        2. Method - get_used_pers_attributes
      2. Populate a string variable by converting JSON to ABAP using below
        1. Class - cl_abap_codepage
        2. Method - convert_from
      3. Now loop at the internal table created in step-1 and carry out string operation to fill the placeholder table where attribute name from step-2, and value after the string operation

       

      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

       

      Author's profile photo Former Member
      Former Member

      Hi Vaibhav,

      I am not allowed to share all logic for PHP, however following hints may help you.

      1. Get the campaign ID like - $campaignId = $_GET['sap-outbound-id']
      2. Build the URL, this is your ODATA url e.g. $url = < ODATA URL>
      3. Concatenate $url  and $campaignId
      4. Build a header basic authorization – $headers = array( 'Authorization: Basic '. base64_encode());
      5. create a new cURL resource ($ch = curl_init()), set the URL and basic authtication using function curl_setopt ()
      6. Execute the URL using curl_exec($ch) and get the HTML contents from ODATA
      7. Carry out simple string operation to get the result e.g. $output = getTextBetweenTags ();
      8. Display the result using echo $output.

      Thanks,

      Debashis

       

      Author's profile photo Solution Development Solution Development
      Solution Development Solution Development

      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?

       

      $ch = curl_init();
      $username = "....";
      $password = ".....";
      $user_agent='Mozilla/5.0 (Windows NT 6.1; rv:8.0) Gecko/20100101 Firefox/8.0';
      $url = "http://server:8010/sap/opu/odata/SAP/ZMKTPRO_SEARCH_BP_SRV/BUPACollectionSet/?$format=json&$filter=IvEmail%20eq%20%27xyz@xyz.com%27";
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
      curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 
      $result = curl_exec($ch); 
      $error = curl_error($ch); 
      curl_close($ch);

      Regards, Roberto

      Author's profile photo Former Member
      Former Member

      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

       

       

      Author's profile photo Taylor Barsamian
      Taylor Barsamian

      Hi Tim,

       

      Great blog! Conversely, is there a way view the email as text?

       

      Thanks!
      Taylor Barsamian

      Author's profile photo Tim Nusch
      Tim Nusch
      Blog Post Author

      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

      Author's profile photo Tim Nusch
      Tim Nusch
      Blog Post Author

      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.

      Author's profile photo Kyoungmi Oh
      Kyoungmi Oh

      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 🙂