Introduction
In this post, we are going to learn to send an email with multiple attachment files to multiple recipients with CC and BCC. We will use the object-oriented programming concept in ABAP for this purpose. Standard ABAP class CL_BCS will be used to create Send Request for email. This post is an elaborated example for sending emails with functions like Browse attachment files on the Presentation server. So, let's begin with our explanation.
Happy reading!
Table of Content
Pre-requisites
- Basic understanding of the OOP concept.
- Knowledge of ABAP syntaxes.
- Basic knowledge of SAP Landscape.
Problem Statement
Write a program that can send an Email to multiple recipients with multiple attachments in SAP ABAP. The program should be able to browse the Presentation server and select multiple files for attachments.
Solution
We will create a pushbutton on Selection Screen to browse files. Selection will also have Fields to enter user-specified Materials, Email addresses, Subject line, and Send Immediately checkbox. We will use SAP standard method CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG in AT SELECTION-SCREEN event to open the file browser. When selecting files we have used a filter to only select TXT files but you can change that filter or remove it if you want to use any type of file as an attachment. Method CL_GUI_FRONTEND_SERVICES=>GUI_UPLOAD will be used to upload selected files from the Presentation server and convert them into binary tables which will then be attached to the Email. Send request for Email will be created using method CL_BCS=>CREATE_PERSISTENT. To create an Email body and attach files to it we are going to use the method CL_DOCUMENT_BCS=>CREATE_DOCUMENTS. Sender and Recipient objects are created using methods CL_SAPUSER_BCS=>CREATE and CL_CAM_ADDRESS_BCS=>CREATE_INTERNET_ADDRESS respectively. Also, I have used SELECT Query dynamically to fetch data from the DDIC table. If you want to read more about the dynamic SELECT Statement then please check out our post on Dynamic SELECT Query.
Source code
*--------------------------------------------------------------------*
REPORT ztestasr_exp06.
*--------------------------------------------------------------------*
*--------------------------------------------------------------------*
* GLOBAL TABLES
*--------------------------------------------------------------------*
TABLES : sscrfields.
*--------------------------------------------------------------------*
* GLOBAL TYPES
*--------------------------------------------------------------------*
TYPES : BEGIN OF t_selscr,
eml TYPE ad_smtpadr,
END OF t_selscr.
*--------------------------------------------------------------------*
* GLOBAL DATA DECLARATION
*--------------------------------------------------------------------*
DATA : gt_filetab TYPE filetable,
gs_selscr TYPE t_selscr,
gv_nfiles TYPE i,
go_cxroot TYPE REF TO cx_root,
gt_mara TYPE TABLE OF mara,
gt_rec TYPE tline_tab,
gs_mara TYPE mara,
gs_rec TYPE tline,
gv_tab_name TYPE tabname16,
gv_where_clause TYPE string,
gv_sub TYPE string,
gv_body TYPE string,
gt_ret TYPE bapiret2_t,
gt_flds TYPE name_feld_tty.
*--------------------------------------------------------------------*
* SELECTION SCREEN
*--------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK __b1__ WITH FRAME TITLE text-h01. "Send Mail with attachment
SELECTION-SCREEN SKIP.
SELECTION-SCREEN BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(12) dlabel1. "Attachments
SELECTION-SCREEN PUSHBUTTON 15(10) bu1 USER-COMMAND bu1_ucomm. "Browse
SELECTION-SCREEN COMMENT 27(20) dlabel2. "N Files selected
SELECTION-SCREEN END OF LINE.
SELECTION-SCREEN SKIP.
PARAMETERS p_matnr TYPE matnr OBLIGATORY. "Material
PARAMETERS p_sub TYPE char255 LOWER CASE OBLIGATORY. "Subject
SELECT-OPTIONS : so_mto FOR gs_selscr-eml LOWER CASE NO INTERVALS OBLIGATORY, "Email TO
so_mcc FOR gs_selscr-eml LOWER CASE NO INTERVALS, "Email CC
so_mbc FOR gs_selscr-eml LOWER CASE NO INTERVALS. "Email BCC
SELECTION-SCREEN SKIP.
PARAMETERS cb_immed TYPE flag AS CHECKBOX USER-COMMAND cb1_ucomm DEFAULT 'X'. "Send Immediately
SELECTION-SCREEN END OF BLOCK __b1__.
*--------------------------------------------------------------------*
INITIALIZATION.
*--------------------------------------------------------------------*
bu1 = 'Browse'.
dlabel1 = 'Attachments'.
dlabel2 = space.
*Definition of class LCL_MAIN
CLASS lcl_main DEFINITION.
PUBLIC SECTION.
*Static contants with Public visibility
CONSTANTS : gc_mail_to TYPE c LENGTH 2 VALUE 'TO',
gc_mail_cc TYPE c LENGTH 2 VALUE 'CC',
gc_mail_bcc TYPE c LENGTH 2 VALUE 'BC'.
*Static method Definition
CLASS-METHODS:
f_get_data
IMPORTING
VALUE(im_tab_name) TYPE tabname16
VALUE(im_field_list) TYPE name_feld_tty
VALUE(im_where_clause) TYPE string
EXPORTING
VALUE(ex_table) TYPE data,
f_browse_files
EXPORTING
VALUE(ex_filetab) TYPE filetable
VALUE(ex_nooffiles) TYPE i,
f_get_attachment
IMPORTING
VALUE(im_file_dest) TYPE string
EXPORTING
VALUE(ex_att_hex) TYPE solix_tab,
f_send_mail
IMPORTING
VALUE(im_receivers) TYPE tline_tab
VALUE(im_body_txt) TYPE string
VALUE(im_filetab) TYPE filetable
VALUE(im_subject) TYPE string
VALUE(im_immed) TYPE flag
RETURNING
VALUE(re_retmsg) TYPE bapiret2_t,
f_display
IMPORTING
VALUE(im_msg) TYPE bapiret2_t.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
*Implementation of class LCL_MAIN
CLASS lcl_main IMPLEMENTATION.
*Implementaion of Method F_DISPALY
METHOD f_get_data.
*Local data declaration.
DATA : lv_field_str TYPE string VALUE IS INITIAL,
lv_tab_name TYPE tabname16 VALUE IS INITIAL,
lv_where_clause TYPE string VALUE IS INITIAL,
lo_results TYPE REF TO data.
FIELD-SYMBOLS : <lfs_table> TYPE ANY TABLE.
*Assign table dynamically
lv_tab_name = im_tab_name.
CREATE DATA lo_results TYPE TABLE OF (lv_tab_name).
ASSIGN lo_results->* TO <lfs_table>.
*Convert field list table into string
LOOP AT im_field_list INTO DATA(ls_field).
IF lv_field_str IS INITIAL.
lv_field_str = ls_field.
ELSE.
lv_field_str = |{ lv_field_str } { ls_field }|.
ENDIF.
ENDLOOP.
lv_where_clause = im_where_clause.
IF lv_field_str IS NOT INITIAL AND
lv_tab_name IS NOT INITIAL AND
<lfs_table> IS ASSIGNED AND
lv_where_clause IS NOT INITIAL.
*Execute dynamic select query
SELECT
(lv_field_str)
FROM (lv_tab_name)
INTO CORRESPONDING FIELDS OF TABLE <lfs_table>
WHERE (lv_where_clause).
ENDIF.
*Return output table dynamically
ex_table = <lfs_table>.
ENDMETHOD.
*Implementaion of Method F_BROWSE_FILES
METHOD f_browse_files.
*Local data declaration
DATA : lt_filetab TYPE filetable,
lv_retcode TYPE i,
lv_user_action TYPE i,
lv_file_encoding TYPE abap_encoding.
*Browse files for attachment
CALL METHOD cl_gui_frontend_services=>file_open_dialog
EXPORTING
window_title = 'Select Attachment'
default_extension = 'TXT'
default_filename = 'TestFile'
file_filter = '(*.txt)|*.txt|'
with_encoding = abap_true
initial_directory = 'C:'
multiselection = abap_true
CHANGING
file_table = lt_filetab
rc = lv_retcode
user_action = lv_user_action
file_encoding = lv_file_encoding
EXCEPTIONS
file_open_dialog_failed = 1
cntl_error = 2
error_no_gui = 3
not_supported_by_gui = 4
OTHERS = 5.
IF lv_retcode GT 0 AND
sy-subrc EQ 0.
ex_filetab = lt_filetab.
ex_nooffiles = lv_retcode.
ENDIF.
ENDMETHOD.
*Implementaion of Method F_GET_ATTACHMENT
METHOD f_get_attachment.
*Local data declaration
DATA : lv_fpath TYPE string,
lv_filelength TYPE i,
lv_xheader TYPE xstring,
lv_isscanned TYPE char01 VALUE IS INITIAL,
lt_att_file TYPE solix_tab.
*Upload file from Presentation server
lv_fpath = im_file_dest.
CALL METHOD cl_gui_frontend_services=>gui_upload
EXPORTING
filename = lv_fpath
filetype = 'BIN'
has_field_separator = space
header_length = 0
read_by_line = 'X'
dat_mode = space
codepage = space
ignore_cerr = abap_true
replacement = '#'
virus_scan_profile = '/SCET/GUI_UPLOAD' "Check T-code VSCANPROFILE
IMPORTING
filelength = lv_filelength
header = lv_xheader
CHANGING
data_tab = lt_att_file "Binary data of selected file
isscanperformed = lv_isscanned
EXCEPTIONS
file_open_error = 1
file_read_error = 2
no_batch = 3
gui_refuse_filetransfer = 4
invalid_type = 5
no_authority = 6
unknown_error = 7
bad_data_format = 8
header_not_allowed = 9
separator_not_allowed = 10
header_too_long = 11
unknown_dp_error = 12
access_denied = 13
dp_out_of_memory = 14
disk_full = 15
dp_timeout = 16
not_supported_by_gui = 17
error_no_gui = 18
OTHERS = 19.
IF sy-subrc EQ 0.
ex_att_hex = lt_att_file.
ENDIF.
ENDMETHOD.
*Implementaion of Method F_SEND_MAIL
METHOD f_send_mail.
*Local data declaration
DATA : lt_rec TYPE tline_tab,
ls_rec TYPE tline,
lv_subject TYPE so_obj_des,
lv_email TYPE ad_smtpadr,
lv_body TYPE string,
lv_file TYPE so_obj_des,
lv_file_str TYPE string,
lv_ext TYPE so_obj_tp,
lv_ext_str TYPE string,
lv_mime TYPE w3conttype,
lv_flag TYPE flag,
lv_immed TYPE flag,
lt_att TYPE solix_tab,
lt_soli TYPE soli_tab,
lt_filetab TYPE filetable,
ls_filename TYPE string,
lt_ret TYPE bapiret2_t,
lo_request TYPE REF TO cl_bcs,
lo_doc TYPE REF TO cl_document_bcs,
lo_sender TYPE REF TO if_sender_bcs,
lo_recipient TYPE REF TO if_recipient_bcs,
lo_cxroot TYPE REF TO cx_root.
lt_rec = im_receivers.
lv_subject = im_subject.
lv_body = im_body_txt.
lv_immed = im_immed.
lt_filetab = im_filetab.
*Create Email send request
TRY.
CALL METHOD cl_bcs=>create_persistent
RECEIVING
result = lo_request.
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
*Create Sender for Email
TRY.
CALL METHOD cl_sapuser_bcs=>create
EXPORTING
i_user = sy-uname
RECEIVING
result = lo_sender.
CATCH cx_address_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
*Set Sender in Email
TRY.
CALL METHOD lo_request->set_sender
EXPORTING
i_sender = lo_sender.
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
LOOP AT lt_rec INTO ls_rec.
*Create Recipients for Email
lv_email = ls_rec-tdline.
TRY.
CALL METHOD cl_cam_address_bcs=>create_internet_address
EXPORTING
i_address_string = lv_email
RECEIVING
result = lo_recipient.
CATCH cx_address_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
CLEAR : lv_email.
*Add Recipients in Email
CASE ls_rec-tdformat.
WHEN gc_mail_bcc. "Recipient in BCC
TRY.
CALL METHOD lo_request->add_recipient
EXPORTING
i_recipient = lo_recipient
i_express = abap_false
i_copy = abap_false
i_blind_copy = abap_true
i_no_forward = abap_false.
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
WHEN gc_mail_cc. "Recipient in CC
TRY.
CALL METHOD lo_request->add_recipient
EXPORTING
i_recipient = lo_recipient
i_express = abap_false
i_copy = abap_true
i_blind_copy = abap_false
i_no_forward = abap_false.
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
WHEN OTHERS. "Recipient in TO
TRY.
CALL METHOD lo_request->add_recipient
EXPORTING
i_recipient = lo_recipient
i_express = abap_true
i_copy = abap_false
i_blind_copy = abap_false
i_no_forward = abap_false.
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
ENDCASE.
CLEAR : lo_recipient.
ENDLOOP.
*Set Email subject max length 255
TRY.
CALL METHOD lo_request->set_message_subject
EXPORTING
ip_subject = |{ lv_subject } - Long Subject|. "Max lenght 255
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
CALL METHOD cl_document_bcs=>string_to_soli
EXPORTING
ip_string = lv_body
RECEIVING
rt_soli = lt_soli.
*Create Mail body
TRY.
CALL METHOD cl_document_bcs=>create_document
EXPORTING
i_type = 'HTM' "OR Valid object type from table TSOTD
i_subject = lv_subject "Max length 50
i_length = '1000' "Size of the document in bytes.
i_language = sy-langu "Lang assigned to document
i_importance = '1' "High priority
i_sensitivity = 'P' "Confidential
i_text = lt_soli "Mail Body
RECEIVING
result = lo_doc.
CATCH cx_document_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
*Upload attachment files from Presentation server
LOOP AT lt_filetab INTO ls_filename.
CALL METHOD f_get_attachment
EXPORTING
im_file_dest = ls_filename
IMPORTING
ex_att_hex = lt_att.
CALL FUNCTION 'CRM_EMAIL_SPLIT_FILENAME'
EXPORTING
iv_path = ls_filename
IMPORTING
ev_filename = lv_file_str
ev_extension = lv_ext_str
ev_mimetype = lv_mime.
*Add attachment files
lv_file = lv_file_str.
lv_ext = lv_ext_str.
TRY.
CALL METHOD lo_doc->add_attachment
EXPORTING
i_attachment_type = lv_ext
i_attachment_subject = lv_file
i_attachment_language = sy-langu
i_att_content_hex = lt_att.
CATCH cx_document_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
CLEAR : lv_file, lv_ext, lv_mime,
lt_att[].
ENDLOOP.
*Set mail body and attachments
TRY.
CALL METHOD lo_request->set_document
EXPORTING
i_document = lo_doc.
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
*Set Immediate send
TRY.
CALL METHOD lo_request->set_send_immediately
EXPORTING
i_send_immediately = lv_immed.
CATCH cx_send_req_bcs INTO lo_cxroot.
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
*Send mail
TRY.
CALL METHOD lo_request->send
EXPORTING
i_with_error_screen = abap_true
RECEIVING
result = lv_flag.
CATCH cx_send_req_bcs .
CALL FUNCTION 'RS_EXCEPTION_TO_BAPIRET2'
EXPORTING
i_r_exception = lo_cxroot
CHANGING
c_t_bapiret2 = re_retmsg.
CLEAR : lo_cxroot.
ENDTRY.
*Commit the send request
COMMIT WORK AND WAIT.
ENDMETHOD.
*Implementaion of Method F_DISPALY
METHOD f_display.
*Local data declaration
DATA : lt_msg TYPE bapiret2_t,
lv_flag TYPE flag VALUE IS INITIAL.
lt_msg = im_msg.
LOOP AT lt_msg TRANSPORTING NO FIELDS WHERE type CA 'AEX'.
lv_flag = abap_true.
EXIT.
ENDLOOP.
IF lv_flag IS INITIAL.
cl_demo_output=>display_text('Mail sent successfully').
ELSE.
cl_demo_output=>display( data = lt_msg
name = 'Failed' ).
ENDIF.
ENDMETHOD.
ENDCLASS.
*--------------------------------------------------------------------*
AT SELECTION-SCREEN.
*--------------------------------------------------------------------*
*On-Click action for Browse button
CASE sscrfields-ucomm.
WHEN 'BU1_UCOMM'.
CALL METHOD lcl_main=>f_browse_files
IMPORTING
ex_filetab = gt_filetab
ex_nooffiles = gv_nfiles.
WHEN 'CB1_UCOMM'.
WHEN OTHERS.
ENDCASE.
*--------------------------------------------------------------------*
AT SELECTION-SCREEN OUTPUT.
*--------------------------------------------------------------------*
*Alter Selection screen text
IF gv_nfiles GT 0.
dlabel2 = |{ gv_nfiles } Files selected|.
ELSE.
dlabel2 = space.
ENDIF.
*--------------------------------------------------------------------*
START-OF-SELECTION.
*--------------------------------------------------------------------*
*Populate table with Field names for
*Dynamic select query.
gt_flds = VALUE #(
( 'MATNR' )
( 'ERSDA' )
( 'VPSTA' )
( 'PSTAT' )
).
gv_tab_name = 'MARA'.
*Prepare where clause for dynamic select
REPLACE ALL OCCURRENCES OF '*' IN p_matnr WITH '%'.
IF sy-subrc IS INITIAL.
gv_where_clause = |MATNR LIKE '{ p_matnr }'|.
ELSE.
gv_where_clause = |MATNR EQ '{ p_matnr }'|.
ENDIF.
TRY .
*Calling method F_GET_DATA of class LCL_MAIN to
*fetch data using Dynamic select query
CALL METHOD lcl_main=>f_get_data
EXPORTING
im_field_list = gt_flds
im_tab_name = gv_tab_name
im_where_clause = gv_where_clause
IMPORTING
ex_table = gt_mara.
CATCH cx_root INTO go_cxroot.
ENDTRY.
*Create Mail body
gv_body = |<p>Dear Recipient</p><p>This a mail body for testing purpose</p><br>|.
LOOP AT gt_mara INTO gs_mara.
gv_body = |{ gv_body }<hr><span>Material: </span><span>{ gs_mara-matnr }</span><br>|.
gv_body = |{ gv_body }<span>Created on: </span><span>{ gs_mara-ersda }</span><br>|.
gv_body = |{ gv_body }<span>VPSTA: </span><span>{ gs_mara-vpsta }</span><br>|.
gv_body = |{ gv_body }<span>PSTAT: </span><span>{ gs_mara-pstat }</span><br>|.
ENDLOOP.
gv_body = |{ gv_body }<hr><br><p>Thanks & Regards</p><p>Kode Solver</p>|.
*Create list of Recipients
LOOP AT so_mto.
gs_rec-tdformat = lcl_main=>gc_mail_to.
gs_rec-tdline = so_mto-low.
APPEND gs_rec TO gt_rec.
CLEAR gs_rec.
ENDLOOP.
LOOP AT so_mcc.
gs_rec-tdformat = lcl_main=>gc_mail_cc.
gs_rec-tdline = so_mcc-low.
APPEND gs_rec TO gt_rec.
CLEAR gs_rec.
ENDLOOP.
LOOP AT so_mbc.
gs_rec-tdformat = lcl_main=>gc_mail_bcc.
gs_rec-tdline = so_mbc-low.
APPEND gs_rec TO gt_rec.
CLEAR gs_rec.
ENDLOOP.
*Send Email with attachments
gv_sub = p_sub.
CALL METHOD lcl_main=>f_send_mail
EXPORTING
im_body_txt = gv_body
im_filetab = gt_filetab
im_immed = abap_true
im_receivers = gt_rec
im_subject = gv_sub
RECEIVING
re_retmsg = gt_ret.
*--------------------------------------------------------------------*
END-OF-SELECTION.
*--------------------------------------------------------------------*
*Display Output message
CALL METHOD lcl_main=>f_display
EXPORTING
im_msg = gt_ret.
Input
Selection Screen
File Browser
Output
Message display
SOST Mail sent
Mail body
List of Attachments
Long Email Subject
Conclusion
We understand that this looks a bit complex but you can understand it by dividing the whole program into separate actions like fetching data, selecting files, converting files for attachments, sending emails, and finally displaying output results. We tried to make this program as flexible as possible so that it can be used in different scenarios. You would just need to do minor tweaks as per your requirements. We hope that this would have been helpful to you. In case you have any doubts or you have any suggestions then please leave a comment or contact us. With this, we shall take our leave.
See you later!