BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
freshstarter
Quartz | Level 8

Hello,

 

I am trying to send an email programmatically from SAS Studio using the Microsoft Graph API. I am able to send the email successfully along with an attachment.

 

However, the main issue is that the attachment is not in a proper format. For PDF, Excel, and ZIP files, I receive an “invalid format” error. Below is the code I used to send a ZIP file via email. Although I do receive the ZIP file, when I try to open it, I get the error: “Windows cannot open the folder. The compressed (zipped) file is invalid.”

 

Could you please advise whether there is an issue in the code or if Microsoft Graph cannot be used to send attachments in the correct format?

filename resp temp;
proc http 
url="https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token"
in='grant_type=client_credentials&client_id=<client_id>&client_secret=<client_secret>&scope=https://graph.microsoft.com/.default' 
ct="application/x-www-form-urlencoded"
out=resp
method='POST';
run;
 
libname auth json fileref=resp;
data _null_;
set auth.root;
call symputx('access_token',access_token);
run;


/*--------------------------------*/
/* Encode attachment              */
/*--------------------------------*/

filename src "/mnt/discovery/ukida/_shared/test.zip" recfm=n;
filename tgt "/tmp/test.zip" recfm=n;

data _null_;
    infile src recfm=n lrecl=32767;
    file tgt recfm=n lrecl=32767;

    length buffer $32767;

    input buffer $char32767.;
    put buffer $char32767. @;   
run;

filename raw "/tmp/test.zip" recfm=n;
filename enc temp;

data _null_;
    infile raw recfm=n end=eof;
    file enc lrecl=76;

    length buffer $57;
    retain buffer '';

    input byte $char1.;
    buffer = cats(buffer, byte);

    if length(buffer) = 57 then do;
        put buffer $base64X76.;
        buffer = '';
    end;

    if eof and length(buffer) > 0 then do;
        put buffer $base64X76.;
    end;
run;
/*--------------------------------*/
/* Load into macro variable       */
/*--------------------------------*/
data _null_;
    infile enc truncover;
    length file_b64 $32767;
    retain file_b64 '';

    input line $char32767.;
    line = compress(line,,'kw');   

    file_b64 = cats(file_b64, line);

    call symputx('file_b64', file_b64, 'G');
run;


%put >>> BASE64 LENGTH = %length(&file_b64);

filename mail_in temp;

data _null_;
    file mail_in lrecl=32767;

    put '{';
    put '  "message": {';
    put '    "subject": "Test SAS Viya email with attachment",';

    put '    "body": {';
    put '      "contentType": "Text",';
    put '      "content": "Hello! This email has an attachment."';
    put '    },';

    put '    "toRecipients": [';
    put '      { "emailAddress": { "address": "[email protected]" } }';
    put '    ],';

    put '    "attachments": [';
    put '      {';
    put '        "@odata.type": "#microsoft.graph.fileAttachment",';
    put '        "name": "test.zip",';
    put '        "contentType": "application/zip",';
    put "        ""contentBytes"": ""&file_b64""";
    put '      }';
    put '    ]';

    put '  },';
    put '  "saveToSentItems": true';
    put '}';
run;

filename sendresp temp;

proc http
  url="https://graph.microsoft.com/v1.0/users/[email protected]/sendMail"
  method="POST"
  in=mail_in
  out=sendresp;
  headers
    "Authorization"="Bearer &access_token"
    "Content-Type"="application/json";
run;


data _null_;
  infile sendresp;
  input;
  put _infile_;
run;

 

1 ACCEPTED SOLUTION

Accepted Solutions
gwootton
SAS Super FREQ

Sounds like the problem is with your code to generate the base64 content, so you are ending up with a bad attachment file.

If you replace your code from "filename src..." to "filename sendresp temp;" with this, does it work? Here I'm building the JSON structure around the base64 encoded file using put and then processing the attachment in 15,000 byte chunks into the contentBytes field, then at the end closing the JSON structure. 

/* Define the source file and the temporary file for the email request payload. */
filename src "/mnt/discovery/ukida/_shared/test.zip" recfm=f lrecl=15000;
filename mailin temp;

data _null_;
    file mailin recfm=n; /* Write the file as a byte stream without any record formatting */
    infile src length=bytes_read truncover end=eof; 
    
    /* Initialize variables */
    length 
        chunk $ 15000
        fmt_string $ 20
        b64_chunk $ 20000
    ;
    
    /* Build the JSON structure for the payload */
    if _n_ = 1 then do;
        put '{' /
            '  "message": {' /
            '    "subject": "Test SAS Viya email with attachment",' /
            '    "body": {' /
            '      "contentType": "Text",' /
            '      "content": "Hello! This email has an attachment." ' /
            '    },' /
            '    "toRecipients": [' /
            '      { "emailAddress": { "address": "[email protected]" } }' /
            '    ],' /
            '    "attachments": [' /
            '      {' /
            '        "@odata.type": "#microsoft.graph.fileAttachment",' /
            '        "name": "test.zip",' /
            '        "contentType": "application/zip",' /
            '        "contentBytes": "'; /* Leave this open */
    end;

    /* Process the attachment in 15000 byte chunks, encoding each chunk to base64 */
    input chunk $char15000.;
    b64_len = ceil(bytes_read / 3) * 4;
    fmt_string = compress("$base64x" || put(b64_len, best.));
    b64_chunk = strip(putc(substr(chunk, 1, bytes_read), fmt_string));
    actual_b64_len = length(b64_chunk);
    /* Write the base64 block directly into the open contentBytes quotes */
    put b64_chunk $varying20000. actual_b64_len @@; 

    /* Close the contentBytes quotes and JSON structure when the end of the file is reached. */
    if eof then do;
        put '"' / 
            '      }' /
            '    ]' /
            '  },' /
            '  "saveToSentItems": "true"' /
            '}';
    end;
run;

 

--
Greg Wootton | Principal Systems Technical Support Engineer

View solution in original post

4 REPLIES 4
gwootton
SAS Super FREQ

Sounds like the problem is with your code to generate the base64 content, so you are ending up with a bad attachment file.

If you replace your code from "filename src..." to "filename sendresp temp;" with this, does it work? Here I'm building the JSON structure around the base64 encoded file using put and then processing the attachment in 15,000 byte chunks into the contentBytes field, then at the end closing the JSON structure. 

/* Define the source file and the temporary file for the email request payload. */
filename src "/mnt/discovery/ukida/_shared/test.zip" recfm=f lrecl=15000;
filename mailin temp;

data _null_;
    file mailin recfm=n; /* Write the file as a byte stream without any record formatting */
    infile src length=bytes_read truncover end=eof; 
    
    /* Initialize variables */
    length 
        chunk $ 15000
        fmt_string $ 20
        b64_chunk $ 20000
    ;
    
    /* Build the JSON structure for the payload */
    if _n_ = 1 then do;
        put '{' /
            '  "message": {' /
            '    "subject": "Test SAS Viya email with attachment",' /
            '    "body": {' /
            '      "contentType": "Text",' /
            '      "content": "Hello! This email has an attachment." ' /
            '    },' /
            '    "toRecipients": [' /
            '      { "emailAddress": { "address": "[email protected]" } }' /
            '    ],' /
            '    "attachments": [' /
            '      {' /
            '        "@odata.type": "#microsoft.graph.fileAttachment",' /
            '        "name": "test.zip",' /
            '        "contentType": "application/zip",' /
            '        "contentBytes": "'; /* Leave this open */
    end;

    /* Process the attachment in 15000 byte chunks, encoding each chunk to base64 */
    input chunk $char15000.;
    b64_len = ceil(bytes_read / 3) * 4;
    fmt_string = compress("$base64x" || put(b64_len, best.));
    b64_chunk = strip(putc(substr(chunk, 1, bytes_read), fmt_string));
    actual_b64_len = length(b64_chunk);
    /* Write the base64 block directly into the open contentBytes quotes */
    put b64_chunk $varying20000. actual_b64_len @@; 

    /* Close the contentBytes quotes and JSON structure when the end of the file is reached. */
    if eof then do;
        put '"' / 
            '      }' /
            '    ]' /
            '  },' /
            '  "saveToSentItems": "true"' /
            '}';
    end;
run;

 

--
Greg Wootton | Principal Systems Technical Support Engineer
freshstarter
Quartz | Level 8

Thank you so much @gwootton . It worked. 

 

I am currently trying to create a single macro that can handle attachments of all file formats in an email. The idea is to be able to call the macro with the required parameters using the code below.

 

I have tried my best to get this working, but I have not been able to complete it successfully. It consistently throws the following error:

 

{"error":{"code":"BadRequest","message":"Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format.","innerError":{"date":"2026-06-05T11:15:43","request-id":"2d98ce2d-3d92-44ad-ae29-dfb7e6917776","client-request-id":"2d98ce2d-3d92-44ad-ae29-dfb7e6917776"}}}
 
It would be very helpful if you could assist me in getting my code below to work. Thank you.

 

 

 

%macro send_mail(
    to=,
    cc=,
    subject=,
    body=,
    attachment=
);

    %local access_token has_cc has_attach content_type attachment_name;
    %let has_cc = %sysfunc(ifc(%length(&cc) > 0, 1, 0));
    %let has_attach = %sysfunc(ifc(%length(&attachment) > 0, 1, 0));

    %let SENDER_EMAIL = [email protected];

    /*--------------------------------*/
    /* Read secrets                   */
    /*--------------------------------*/
    %macro read_secret(file,macvar);
    data _null_;
        infile "&file" lrecl=32767 truncover;
        input _value_ $char32767.;
        call symputx("&macvar", strip(_value_), 'G');
    run;
    %mend;

    %read_secret(/secrets/sasmail_server_client_id,CLIENT_ID);
    %read_secret(/secrets/sasmail_server_client_secret,CLIENT_SECRET);
    %read_secret(/secrets/entra_id_tenant_id,TENANT_ID);

    /*--------------------------------*/
    /* Get OAuth token                */
    /*--------------------------------*/
    filename resp temp;

    proc http
        url="https://login.microsoftonline.com/&TENANT_ID/oauth2/v2.0/token"
        method="POST"
        in="grant_type=client_credentials%str(&)
            client_id=&CLIENT_ID%str(&)
            client_secret=&CLIENT_SECRET%str(&)
            scope=https://graph.microsoft.com/.default"
        ct="application/x-www-form-urlencoded"
        out=resp;
    run;

    libname auth json fileref=resp;

    data _null_;
        set auth.root;
        call symputx('access_token', access_token);
    run;


    %if &has_attach %then %do;

        data _null_;
            length fname $256 ext $10 ctype $100;

            fname = "&attachment";
            attachment_name = scan(fname, -1, '/\');
            ext = lowcase(scan(fname, -1, '.'));

            select (ext);
                when ('csv')   ctype='text/csv';
                when ('txt')   ctype='text/plain';
                when ('zip')   ctype='application/zip';
                when ('doc')   ctype='application/msword';
                when ('docx')  ctype='application/vnd.openxmlformats-officedocument.wordprocessingml.document';
                when ('pdf')   ctype='application/pdf';
                when ('xls')   ctype='application/vnd.ms-excel';
                when ('xlsx')  ctype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                when ('ppt')   ctype='application/vnd.ms-powerpoint';
                when ('pptx')  ctype='application/vnd.openxmlformats-officedocument.presentationml.presentation';
                when ('html','htm') ctype='text/html';
                otherwise ctype='application/octet-stream';
            end;

            call symputx('content_type', ctype);
            call symputx('attachment_name', attachment_name);
        run;

    %end;

    /*--------------------------------*/
    /* Build JSON with attachment     */
    /*--------------------------------*/
    filename mail temp;

    %if &has_attach %then %do;

        filename src "&attachment" recfm=f lrecl=15000;

        data _null_;
            file mail recfm=n;

            length chunk $15000 b64_chunk $20000 fmt_string $20;

            if _n_ = 1 then do;

                subject_txt = tranwrd(symget('subject'), '"', '\"');
                body_txt    = tranwrd(symget('body'),    '"', '\"');

                put '{'
                    ' "message": {'
                    '  "subject": "' subject_txt '",'
                    '  "body": {'
                    '   "contentType": "HTML",'
                    '   "content": "' body_txt '"'
                    '  },'
                    '  "toRecipients": [';

                
                %do i=1 %to %sysfunc(countw(&to));
                    %if &i>1 %then put ",";
                    put ' { "emailAddress": { "address": "%scan(&to,&i)" } }';
                %end;

                put ']';

               
                %if &has_cc %then %do;
                    put ',"ccRecipients": [';
                    %do j=1 %to %sysfunc(countw(&cc));
                        %if &j>1 %then put ",";
                        put ' { "emailAddress": { "address": "%scan(&cc,&j)" } }';
                    %end;
                    put ']';
                %end;

               
                put ',"attachments":[{'
                    '"@odata.type":"#microsoft.graph.fileAttachment",'
                    '"name":"' "&attachment_name" '",'
                    '"contentType":"' "&content_type" '",'
                    '"contentBytes":"';
            end;

            infile src recfm=f lrecl=15000 end=eof length=bytes_read;

            input chunk $char15000.;

            b64_len = ceil(bytes_read/3)*4;
            fmt_string = cats('$base64x', b64_len);
            b64_chunk = putc(substr(chunk,1,bytes_read), fmt_string);

            put b64_chunk $varying20000. length(b64_chunk) @@;

            if eof then do;
                put '"}]},"saveToSentItems":"true"}';
            end;
        run;

    %end;
    %else %do;

 
        data _null_;
            file mail lrecl=32767;

            subject_txt = tranwrd(symget('subject'), '"', '\"');
            body_txt    = tranwrd(symget('body'),    '"', '\"');

            put '{';
            put ' "message": {';
            put '  "subject": "' subject_txt '",';
            put '  "body": {';
            put '   "contentType": "HTML",';
            put '   "content": "' body_txt '"';
            put '  },';
            put '  "toRecipients": [';

            %do i=1 %to %sysfunc(countw(&to));
                %if &i>1 %then put ",";
                put ' { "emailAddress": { "address": "%scan(&to,&i)" } }';
            %end;

            put ']';

            %if &has_cc %then %do;
                put ',"ccRecipients": [';
                %do j=1 %to %sysfunc(countw(&cc));
                    %if &j>1 %then put ",";
                    put ' { "emailAddress": { "address": "%scan(&cc,&j)" } }';
                %end;
                put ']';
            %end;

            
            put ' },';
            put ' "saveToSentItems": true';
            put '}';

        run;

    %end;

    /*--------------------------------*/
    /* Send email                     */
    /*--------------------------------*/
    filename sendresp temp;

    proc http
        url="https://graph.microsoft.com/v1.0/users/&SENDER_EMAIL/sendMail"
        method="POST"
        in=mail
        out=sendresp;
        headers
            "Authorization"="Bearer &access_token"
            "Content-Type"="application/json";
    run;

    data _null_;
        infile sendresp;
        input;
        putlog _infile_;
    run;

%mend send_mail;

data null;
    format dte ddmmyy10. tme time8.;
    dt = datetime();

call symputx(
    'mail_body',
    catx(
        '',
        '<html><body>',
        '<p>Hello,</p>',
        '<p>The scheduled job completed successfully on ',
        put(datepart(dt), ddmmyy10.),
        ' at ',
        put(timepart(dt), time8.),
        '.</p>',
        '<p>Thank you,<br>',
        'Support Team</p>',
        '</body></html>'
    ),
    'L'
);
run;

%send_mail(
    [email protected] [email protected],
    subject=Test SAS Extract Completed Successfully,
    body=%superq(mail_body)
    attachment=/mnt/discovery/ukida/_shared/test14.zip
);

 

gwootton
SAS Super FREQ
I would recommend you start by looking at the generated payload file and making sure it's valid JSON being generated.
For example, change "filename mail temp" to "filename mail /tmp/mail.json" and then open the file with an editor that checks JSON syntax.
--
Greg Wootton | Principal Systems Technical Support Engineer
freshstarter
Quartz | Level 8

I have finally made this work. Thanks @gwootton  for your help.

 

%macro send_mail(
    to=,
    cc=,
    subject=,
    body=,
    attachment=
);

    %local access_token has_cc has_attach content_type attachment_name
           n_to n_cc i j;

    %let has_cc     = %sysfunc(ifc(%length(&cc)         > 0, 1, 0));
    %let has_attach = %sysfunc(ifc(%length(&attachment) > 0, 1, 0));
    %let SENDER_EMAIL = [email protected];

    /* ── Read secrets ─────────────────────────────────────────── */
    %macro read_secret(file, macvar);
        data _null_;
            infile "&file" lrecl=32767 truncover;
            input _value_ $char32767.;
            call symputx("&macvar", strip(_value_), 'G');
        run;
    %mend;

    %read_secret(/var/secrets/sasmail_server_client_id,     CLIENT_ID)
    %read_secret(/var/secrets/sasmail_server_client_secret, CLIENT_SECRET)
    %read_secret(/var/secrets/entra_id_tenant_id,           TENANT_ID)

    /* ── OAuth token ──────────────────────────────────────────── */
    filename tkn temp;

    proc http
        url    = "https://login.microsoftonline.com/&TENANT_ID/oauth2/v2.0/token"
        method = "POST"
        in     = "grant_type=client_credentials%str(&)client_id=&CLIENT_ID%str(&)client_secret=&CLIENT_SECRET%str(&)scope=https://graph.microsoft.com/.default"
        ct     = "application/x-www-form-urlencoded"
        out    = tkn;
    run;

    libname jt json fileref=tkn;
    data _null_;
        set jt.root;
        call symputx('access_token', access_token, 'L');
    run;
    libname jt clear;
    filename tkn clear;

    %if %length(&access_token) = 0 %then %do;
        %put ERROR: Token acquisition failed;
        %return;
    %end;

    /* ── Attachment metadata ──────────────────────────────────── */
    %if &has_attach %then %do;
        data _null_;
            length fname $512 ext $20 ctype $100 aname $256;
            fname = "&attachment";
            aname = scan(fname, -1, '/\');
            ext   = lowcase(scan(aname, -1, '.'));
            select (ext);
                when ('csv')        ctype = 'text/csv';
                when ('txt')        ctype = 'text/plain';
                when ('htm','html') ctype = 'text/html';
                when ('zip')        ctype = 'application/zip';
                when ('pdf')        ctype = 'application/pdf';
                when ('doc')        ctype = 'application/msword';
                when ('docx')       ctype = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
                when ('xls')        ctype = 'application/vnd.ms-excel';
                when ('xlsx')       ctype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                when ('ppt')        ctype = 'application/vnd.ms-powerpoint';
                when ('pptx')       ctype = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
                otherwise           ctype = 'application/octet-stream';
            end;
            call symputx('content_type',    ctype, 'L');
            call symputx('attachment_name', aname, 'L');
        run;
    %end;

    /* ── Recipient counts ─────────────────────────────────────── */
    %let n_to = %sysfunc(countw(&to,  %str( )));
    %let n_cc = %sysfunc(countw(&cc,  %str( )));

    /* ── Pre-resolve each address into its own macro variable ─── */
    %do i = 1 %to &n_to;
        %local to_&i;
        %let to_&i = %scan(&to, &i, %str( ));
    %end;
    %if &has_cc %then %do;
        %do j = 1 %to &n_cc;
            %local cc_&j;
            %let cc_&j = %scan(&cc, &j, %str( ));
        %end;
    %end;


    filename mail_out temp recfm=n;

    /* ── STEP 1: Write JSON header ────────────────────────────── */
    data _null_;
        file mail_out recfm=n mod;

        length subject_esc $2000 body_chunk $32767 body_full $32767
               attname $256 conttype $100;

        subject_esc = symget('subject');
        subject_esc = tranwrd(subject_esc, '\',  '\\');
        subject_esc = tranwrd(subject_esc, '"',  '\"');

        put '{"message":{"subject":"' subject_esc +(-1)
            '","body":{"contentType":"HTML","content":"' @@;

        body_full = symget('body');
        total_len = length(body_full);
        pos = 1;
        do while (pos <= total_len);
            seg_len    = min(32000, total_len - pos + 1);
            body_chunk = substr(body_full, pos, seg_len);
            body_chunk = tranwrd(body_chunk, '\',  '\\');
            body_chunk = tranwrd(body_chunk, '"',  '\"');
            body_chunk = tranwrd(body_chunk, '0A'x, '\n');
            body_chunk = tranwrd(body_chunk, '0D'x, '\r');
            put body_chunk $varying32767. seg_len @@;
            pos = pos + seg_len;
        end;

        put '"},"toRecipients":[' @@;
        %do i = 1 %to &n_to;
            %if &i > 1 %then put ',' @@; ;
            put '{"emailAddress":{"address":"' "&&to_&i" '"}}' @@;
        %end;
        put ']' @@;

        %if &has_cc %then %do;
            put ',"ccRecipients":[' @@;
            %do j = 1 %to &n_cc;
                %if &j > 1 %then put ',' @@; ;
                put '{"emailAddress":{"address":"' "&&cc_&j" '"}}' @@;
            %end;
            put ']' @@;
        %end;

        %if &has_attach %then %do;
            attname  = symget('attachment_name');
            conttype = symget('content_type');
            put ',"attachments":[{"@odata.type":"#microsoft.graph.fileAttachment","name":"'
                attname +(-1) '","contentType":"' conttype +(-1)
                '","contentBytes":"' @@;
        %end;
        %else %do;
            put '},"saveToSentItems":"true"}' @@;
        %end;

    run;

    /* ── STEP 2: Append base64 encoded attachment ─────────────── */
    %if &has_attach %then %do;

        /*
           recfm=f lrecl=1 reads exactly one raw byte per iteration.
           This is the most reliable binary read on SAS Viya Linux —
           avoids fopen/fget text-mode corruption of binary content.
        */
        filename att_src "&attachment" recfm=f lrecl=1;
        filename b64_tmp temp recfm=n;

        data _null_;
            file   b64_tmp recfm=n;
            infile att_src recfm=f lrecl=1 end=eof;

            length raw_byte $1 raw_buf $3000 b64_buf $4000;
            retain raw_buf '' buf_len 0;

            input raw_byte $char1.;

            buf_len + 1;
            substr(raw_buf, buf_len, 1) = raw_byte;

            /* When buffer is full encode and flush */
            if buf_len = 3000 then do;
                b64_buf = putc(raw_buf, '$base64x4000');
                put b64_buf $char4000. @@;
                raw_buf = repeat('00'x, 2999);
                buf_len = 0;
            end;

            /* On last byte flush remaining partial buffer */
            if eof and buf_len > 0 then do;
                b64_len = ceil(buf_len / 3) * 4;
                b64_buf = putc(
                    substr(raw_buf, 1, buf_len),
                    cats('$base64x', b64_len)
                );
                put b64_buf $varying4000. b64_len @@;
            end;
        run;

        filename att_src clear;

        /* Copy b64 content into mail_out */
        data _null_;
            file   mail_out recfm=n mod;
            infile b64_tmp  recfm=n end=eof;
            length chunk $4000;
            input chunk $char4000.;
            len = length(chunk);
            put chunk $varying4000. len @@;
        run;

        filename b64_tmp clear;

        /* ── STEP 3: Append JSON footer ───────────────────────── */
        data _null_;
            file mail_out recfm=n mod;
            put '"}]},"saveToSentItems":"true"}' @@;
        run;

    %end;

    /* ── POST to Microsoft Graph ──────────────────────────────── */
    filename resp temp;

    proc http
        url    = "https://graph.microsoft.com/v1.0/users/&SENDER_EMAIL/sendMail"
        method = "POST"
        in     = mail_out
        out    = resp;
        headers
            "Authorization" = "Bearer &access_token"
            "Content-Type"  = "application/json";
    run;

    %put STATUS: &=SYS_PROCHTTP_STATUS_CODE;

    data _null_;
        infile resp;
        input;
        putlog _infile_;
    run;

    /* ── Tidy up ──────────────────────────────────────────────── */
    filename mail_out clear;
    filename resp     clear;

%mend send_mail;


/* ══════════════════════════════════════════════════════════════
   EXAMPLE CALL
   ══════════════════════════════════════════════════════════════ */
data _null_;
    dt = datetime();
    call symputx(
        'mail_body',
        catx('',
            '<html><body>',
            '<p>Hello,</p>',
            '<p>The scheduled job completed successfully on ',
            put(datepart(dt), ddmmyy10.),
            ' at ',
            put(timepart(dt), time8.),
            '.</p>',
            '<p>Thank you,<br>Support Team</p>',
            '</body></html>'
        ),
        'L'
    );
run;

%send_mail(
    to         = [email protected],
    subject    = Test SAS Extract Completed Successfully,
    body       = %superq(mail_body),
    attachment = /mnt/discovery/ukida/_shared/test14.zip
)

CFC_SAS_Communities_400x225.jpg

Call for content now open!

It's your turn to help shape SAS Innovate 2027. Share your expertise and inspire the SAS community.

Submit your proposal →

SAS Viya: Coding Simplified

Carleigh Jo Crabtree presents a simplified intro to SAS Viya for programmers.

Find more tutorials on the SAS Users YouTube channel.

Discussion stats
  • 4 replies
  • 958 views
  • 4 likes
  • 2 in conversation