BookmarkSubscribeRSS Feed
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": "test@org.com" } }';
    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/sasviya_dev@org.com/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 REPLY 1
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": "test@org.com" } }' /
            '    ],' /
            '    "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