Page 1 of 2

Classic Outlook API vs Outlook 365 API

Posted: Thu Feb 26, 2026 8:33 pm
by Rick Lipkin

To All

I have a corporate Customer that has been using Classic MS Outlook and this code has worked fine up until my CUstomer has upgraded several workstations to MS and Outlook 365 .. We have determined that the Classic Outlook computers sent the e-mail but the same program running on computers that have Outlook 365 do not get sent .. here is the Original code that works with Classic Outlook

Code: Select all | Expand

Func Main()

Local cSubject,cTo,cOther,cBody,cCC
Local oOutlook,oMailItem

cTo      := "[email protected]"
cOther   := "[email protected]"
cCC      := "[email protected];[email protected]"
cSubject := "Travel Train Request for Rick Lipkin, Dates 06/01/2020 thru 06/10/2020"
cBody    := "Dear Supervisor : John ( Tommy ) Watson;"+chr(10)
cBody    += "  "+chr(10)
cBody    += "  "+chr(10)
cBody    += "A Travel /  Train request has been submitted for your review and approval. Please click on the Link"+chr(10)
cBody    += "Provided below."+chr(10)
cBody    += " "+chr(10)
cBody    += "Summary of Request:"+chr(10)
cBody    += "  "+chr(10)
cBody    += "   *  Employee Name:     Rick Lipkin"+chr(10)
cBody    += "   *  Vendor Sponsor:    Microsoft"+chr(10)
cBody    += "   *  Conference/Event:  Building an Asure Network"+chr(10)
cBody    += "   *  Location:  Columbia SC"+chr(10)
cBody    += "   *  Start Date:  06/01/2020"+chr(10)
cBody    += "   *  End Date:  06/10/2020"+chr(10)
cBody    += "   *  Estimated Cost:  $500.00"+chr(10)
cBody    += "   *  Is This Associated with a Previously approved Designation? : No"+chr(10)
cBody    += "  "+chr(10)
cBody    += "  "+chr(10)
* cBody    += "file://printserver/scid/0000%20Human%20Resources/Master%20Leave%20Program/%20LeaveW32.Exe"
cBody    += "  "+chr(10)
cBody    += "  "+chr(10)
cBody    += "  "+chr(10)
cBody    += "  "+chr(10)
cBody    += "  "+chr(10)
cBody    += "  "+chr(10)
cBody    += "Note .. This is an automated e-mail sent from the Leave / Travel Program"+chr(10)
cBody    += "  "+chr(10)


oOutLook  := TOleAuto():New("Outlook.Application")   // works with Classic not Outlook 365
oMailItem := oOutLook:Invoke("CreateItem", 0)

oMailitem:to := cTo
*oMailItem:Recipients:Add( cOther )
oMailitem:CC := cCC
oMailItem:Subject := cSubject
oMailItem:Body := cBody

*if ! empty(cPdf)
* oMailItem:Attachments:Add(cPdf)
*endif

oMailItem:display(.F.)
oMailItem:Invoke("Send")
sysrefresh()

msginfo( "E-mail Sent" )

return .T.

I would appreciate if anyone has the Outlook 365 API code that will send the e-mail with 365 installed.

Many thanks
Rick Lipkin


Re: Classic Outlook API vs Outlook 365 API

Posted: Thu Feb 26, 2026 9:55 pm
by FiveWiDi

Hola Rick,

Yo estoy usando lo mismo, pero solicito que el usuario tenga Outlook en uso/encendido/funcionando, y no indico 'oMailItem:display(.F.)'.

La última vez que lo probé funcionaba con Outlook 2010 y con el 365.


Re: Classic Outlook API vs Outlook 365 API

Posted: Thu Feb 26, 2026 10:44 pm
by Rick Lipkin

Carlos .. Thanks for your answer .. unfortunately this is large state Agency and I can not guarantee that all 500 people will have outlook open .. was hoping there was an api I could use to tap into Outlook 365 and send un-attended

Rick Lipkin


Re: Classic Outlook API vs Outlook 365 API

Posted: Fri Feb 27, 2026 8:05 am
by Ruth

Dear Rick,
I worked with this some time ago but haven´t used it lately...not sure if microsoft has made changes since etc ... but maybe it can be of help ...
Kind regards
Ruth

Code: Select all | Expand

#include "c:\harbour\contrib\hbcurl\hbcurl.ch"
#define HB_CURLOPT_POST                      47
#define HB_CURLOPT_URL                        2
#define HB_CURLOPT_DL_BUFF_SETUP              1008
#define HB_CURLOPT_POSTFIELDS                 15
#define HB_CURLOPT_WRITEFUNCTION              11

FUNCTION Main()
    LOCAL cToken, cResult
 cToken := GetAccessToken()
  IF !EMPTY(cToken)
  cResult := SendEmail(cToken)
    ?"Email Send Result: ", cResult
  ELSE
    ? "Failed to retrieve access token."
  ENDIF
 

   
FUNCTION GetAccessToken() LOCAL hCurl := CURL_easy_init() LOCAL cPayload := "" LOCAL cToken := "" LOCAL cTest := "" LOCAL nStartPos, nEndPos LOCAL aHeaders := {"Content-Type: application/x-www-form-urlencoded"} LOCAL cTenantID := hb_GetEnv("TENANT_ID") LOCAL cClientId := hb_GetEnv("365client_id") LOCAL cClientSecret := hb_GetEnv("365client_secret") IF hCurl != NIL CURL_easy_setopt(hCurl, HB_CURLOPT_URL, "https://login.microsoftonline.com/"+cTenantID+"/oauth2/v2.0/token") CURL_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, aHeaders) CURL_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, "grant_type=client_credentials&client_id="+cClientId+"&client_secret="+cClientSecret+"&scope=https://graph.microsoft.com/.default") CURL_easy_setopt(hCurl, HB_CURLOPT_POST, .T.) curl_easy_setopt( hCurl, HB_CURLOPT_DL_BUFF_SETUP ) CURL_easy_setopt(hCurl, HB_CURLOPT_WRITEFUNCTION, @cPayload) // Buffer the response
CURL_easy_perform(hCurl) cPayload := curl_easy_dl_buff_get(hCurl) // Get the response from the buffer nStartPos := AT('"access_token":"',cPayload)+LEN('"access_token":"') nEndPos := hb_AT('"', cPayload, nStartPos) cToken := SUBSTR(cPayload, nStartPos, nEndPos - nStartPos) CURL_easy_cleanup(hCurl) ELSE ? "Failed to initialize cURL session." ENDIF RETURN cToken FUNCTION SendEmail(cToken) LOCAL hCurl := CURL_easy_init() LOCAL cEmailJson LOCAL cResponse := "" LOCAL aHeaders := {} LOCAL cImgBase64 := "" cImgBase64 := hb_base64Encode(hb_MemoRead("c:\www\htdocs\submarine_yellow\img\latest_news2.jpg")) ? "cImgBase64", cImgBase64 cEmailJson := '{"message": {"subject": "Test base64", '; + '"body": {"contentType": "html", '; + '"content": "<h1>test</h1><p>The new cafeteria is open...</p>'; + '<img src=\"data:image/jpeg;base64,' + cImgBase64 + '\" > '; + '"}, '; + '"toRecipients": [{"emailAddress": {"address": "testrecip@****.info"}}]}, '; + '"saveToSentItems": "true"}'
IF hCurl != NIL AADD(aHeaders, "Authorization: Bearer " + cToken) AADD(aHeaders, "Content-Type: application/json") CURL_easy_setopt(hCurl, HB_CURLOPT_URL, "https://graph.microsoft.com/v1.0/users/test@*****.at/sendMail") CURL_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, aHeaders) CURL_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, cEmailJson) CURL_easy_setopt(hCurl, HB_CURLOPT_POST, .T.) CURL_easy_setopt(hCurl, HB_CURLOPT_WRITEFUNCTION, @cResponse) // Buffer the response CURL_easy_perform(hCurl) cResponse := curl_easy_dl_buff_get(hCurl) // Get the response from the buffer CURL_easy_cleanup(hCurl) ? "response", cResponse ELSE cResponse := "Failed to initialize cURL session." ENDIF RETURN cResponse


Re: Classic Outlook API vs Outlook 365 API

Posted: Fri Feb 27, 2026 3:56 pm
by Antonio Linares

Dear Rick,

I have asked gemini 3.1 about it and it came with two solutions. Please let me know if you can access this url:

https://gemini.google.com/share/543bdf310e33

It seems to me that they should work. I wait for your comments :idea:


Re: Classic Outlook API vs Outlook 365 API

Posted: Fri Feb 27, 2026 3:59 pm
by Antonio Linares

Code: Select all | Expand

FUNCTION SendSmtpDesatendido( cDe, cPara, cAsunto, cCuerpo, cPassword )
   LOCAL oCfg, oMsg
   LOCAL cSchema := "http://schemas.microsoft.com/cdo/configuration/"

   TRY
      oMsg := CreateObject("CDO.Message")
      oCfg := CreateObject("CDO.Configuration")

  // Configuración del Servidor SMTP de Office 365
  oCfg:Fields:Item( cSchema + "sendusing"      ):Value := 2 // cdoSendUsingPort
  oCfg:Fields:Item( cSchema + "smtpserver"     ):Value := "smtp.office365.com"
  oCfg:Fields:Item( cSchema + "smtpserverport" ):Value := 587
  oCfg:Fields:Item( cSchema + "smtpusessl"     ):Value := .T.
  oCfg:Fields:Item( cSchema + "smtpauthenticate" ):Value := 1 // cdoBasic
  oCfg:Fields:Item( cSchema + "sendusername"   ):Value := cDe
  oCfg:Fields:Item( cSchema + "sendpassword"   ):Value := cPassword
  oCfg:Fields:Update()

  oMsg:Configuration := oCfg
  oMsg:From    := cDe
  oMsg:To      := cPara
  oMsg:Subject := cAsunto
  oMsg:TextBody := cCuerpo

  oMsg:Send()
  ? "Correo enviado correctamente por SMTP (Desatendido)"

   CATCH oErr
      ? "Error al enviar: " + oErr:Description
   END
RETURN NIL

Re: Classic Outlook API vs Outlook 365 API

Posted: Fri Feb 27, 2026 4:38 pm
by Rick Lipkin

Antonio

I appreciate your quick possible solutions .. just responded back to you on how to determine the how to get the values of the function parameters ?

SendSmtpDesatendido( cDe, cPara, cAsunto, cCuerpo, cPassword )

Again .. thanks for your quick response ..

Rick Lipkin


Re: Classic Outlook API vs Outlook 365 API

Posted: Fri Feb 27, 2026 4:47 pm
by Rick Lipkin

Ruth

Looking at your response .. can you give me an idea how you created some of the parameters .. like

cToken := GetAccessToken()

What is GetAccessToken()

Thanks
Rick Lipkin


Re: Classic Outlook API vs Outlook 365 API

Posted: Fri Feb 27, 2026 6:00 pm
by Antonio Linares
Rick Lipkin wrote: Fri Feb 27, 2026 4:38 pm

Antonio

I appreciate your quick possible solutions .. just responded back to you on how to determine the how to get the values of the function parameters ?

SendSmtpDesatendido( cDe, cPara, cAsunto, cCuerpo, cPassword )

Again .. thanks for your quick response ..

Rick Lipkin

Dear Rick,

( cDe, cPara, cAsunto, cCuerpo, cPassword )

cFrom, cTo, cSubject, cBody, cPassword


Re: Classic Outlook API vs Outlook 365 API

Posted: Fri Feb 27, 2026 9:09 pm
by Ruth

Dear Rick,

I hope to remember correctly ... cToken should be the OAuth2 access token that Microsoft Graph requires in the request header

Authorization: Bearer <access_token>

.

My goal was unattended sending (no interactive user login), therefore I used the OAuth2 client_credentials flow to request an app-only access token from Entra ID (Azure AD). The function below calls the /token endpoint and extracts "access_token" from the JSON response.

Kind regards,
Ruth

Code: Select all | Expand

FUNCTION GetAccessToken()
    LOCAL hCurl := CURL_easy_init()
    LOCAL cPayload := ""
    LOCAL cToken := ""
    LOCAL cTest := ""
    LOCAL nStartPos, nEndPos
    LOCAL aHeaders := {"Content-Type: application/x-www-form-urlencoded"}
    LOCAL cTenantID := hb_GetEnv("TENANT_ID")
    LOCAL cClientId := hb_GetEnv("365client_id")
    LOCAL cClientSecret := hb_GetEnv("365client_secret")
    IF hCurl != NIL
        CURL_easy_setopt(hCurl, HB_CURLOPT_URL, "https://login.microsoftonline.com/"+cTenantID+"/oauth2/v2.0/token")
        CURL_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, aHeaders)
        CURL_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, "grant_type=client_credentials&client_id="+cClientId+"&client_secret="+cClientSecret+"&scope=https://graph.microsoft.com/.default")
        CURL_easy_setopt(hCurl, HB_CURLOPT_POST, .T.)
        curl_easy_setopt( hCurl, HB_CURLOPT_DL_BUFF_SETUP )
        CURL_easy_setopt(hCurl, HB_CURLOPT_WRITEFUNCTION, @cPayload) // Buffer the response
       

CURL_easy_perform(hCurl) cPayload := curl_easy_dl_buff_get(hCurl) // Get the response from the buffer nStartPos := AT('"access_token":"',cPayload)+LEN('"access_token":"') nEndPos := hb_AT('"', cPayload, nStartPos) cToken := SUBSTR(cPayload, nStartPos, nEndPos - nStartPos) CURL_easy_cleanup(hCurl) ELSE ? "Failed to initialize cURL session." ENDIF RETURN cToken

Re: Classic Outlook API vs Outlook 365 API

Posted: Mon Mar 02, 2026 11:03 am
by Ruth

Dear friends,

I stumbled across this today...maybe it is of help for this topic:
https://techcommunity.microsoft.com/blo ... ne/4489835

Kind regards,
Ruth


Re: Classic Outlook API vs Outlook 365 API

Posted: Thu Mar 05, 2026 4:37 pm
by Lailton

Offic365 API:

Code: Select all | Expand

/fivewin/samples/outlook/testoutlook.prg

it is simple and easy to use 8)
It is already built in with Fwh


Re: Classic Outlook API vs Outlook 365 API

Posted: Thu Mar 05, 2026 6:09 pm
by Rick Lipkin

Lailton

Unfortunately my older version of FW does not have that file .. would you mind posting it here on the forum for me .. I would be very grateful !! I have fwh 2404 .. do I need to upgrade ??

Thanks
Rick Lipkin


Re: Classic Outlook API vs Outlook 365 API

Posted: Thu Mar 05, 2026 8:24 pm
by Antonio Linares

Dear Rick,

Lailton's testoutlook.prg example involves using new classes TOutlookMail and TOAuth.

They are not simple classes, thus it is better if you use them from FWH.


Re: Classic Outlook API vs Outlook 365 API

Posted: Thu Mar 05, 2026 9:14 pm
by Rick Lipkin

Antonio

Just sent you an order to upgrade my current FWH32 .. Is the Testoutlook.prg included in the FW samples ?? that use the 365 api ??

Thanks
Rick Lipkin