Skip to Content
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.

* +-------------------------------------------------------------------------------------------------+
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_EXEC_HASH                   TYPE        /BOBF/CONF_KEY
* | [<-()] RV_CONTENT_ID                  TYPE        CUAN_ME_ENGAGEMENT_ID
* +-------------------------------------------------------------------------------------------------+

      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.

        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
        et_target_key  = DATA(lt_action_key) ).

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


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:

* +-------------------------------------------------------------------------------------------------+
* +-------------------------------------------------------------------------------------------------+
* | [--->] 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.

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

    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( ).


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.

You must be Logged on to comment or reply to a post.
    • 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.


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



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

    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.
    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.
    *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 ).
    *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.






    • Hi Debashis,

      1. I used GET_ENTITY instead of GET_ENTITYSET and passed the outbound id like this:
        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,


  • 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=>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

  • 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,


  • 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":{
        • "AS":{
          • "DATE_OF_BIRTH":"19840925",
          • "LANGUAGE":"F",
          • "NAME_FIRST":"Denis",
          • "SMTP_ADDR":""





    Data ls_placeholders    LIKE LINE OF lt_placeholders.

    ls_placeholders-ds_attribute_name = BO-CUAN_INTERACTION_CONTACT-NAME_FIRST

    ls_placeholders-ds_attribute_value = ""

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






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




    • 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&$";
      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); 

      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;






    • Hi Taylor,

      that requirement is not included in the custom solution and has to be built separately or on top of it.

      Best regards,


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



    (404 ERROR)



    SJ from GCDM 🙂