FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour Classic Outlook API vs Outlook 365 API
Posts: 2706
Joined: Fri Oct 07, 2005 01:50 PM
Classic Outlook API vs Outlook 365 API
Posted: Thu Feb 26, 2026 08:33 PM

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

Func Main()

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

cTo      := "twatson@doi.sc.gov"
cOther   := "watsonjt@mindspring.com"
cCC      := "r1.1955@live.com;watsonjt@mindspring.com"
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

Posts: 1445
Joined: Mon Oct 10, 2005 02:38 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Thu Feb 26, 2026 09:55 PM

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.

Un Saludo

Carlos G.



FiveWin 25.12 + Harbour 3.2.0dev (r2502110321), BCC 7.7 Windows 11 Home

Posts: 2706
Joined: Fri Oct 07, 2005 01:50 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Thu Feb 26, 2026 10:44 PM

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

Posts: 179
Joined: Fri Dec 07, 2007 01:26 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Fri Feb 27, 2026 08:05 AM

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

#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
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Fri Feb 27, 2026 03:56 PM

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:

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Fri Feb 27, 2026 03:59 PM
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
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 2706
Joined: Fri Oct 07, 2005 01:50 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Fri Feb 27, 2026 04: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

Posts: 2706
Joined: Fri Oct 07, 2005 01:50 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Fri Feb 27, 2026 04:47 PM

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

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Fri Feb 27, 2026 06:00 PM
Rick Lipkin wrote:

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

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 179
Joined: Fri Dec 07, 2007 01:26 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Fri Feb 27, 2026 09:09 PM

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

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
Posts: 179
Joined: Fri Dec 07, 2007 01:26 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Mon Mar 02, 2026 11:03 AM

Dear friends,

I stumbled across this today...maybe it is of help for this topic:
https://techcommunity.microsoft.com/blog/exchange/updated-exchange-online-smtp-auth-basic-authentication-deprecation-timeline/4489835

Kind regards,
Ruth

Posts: 231
Joined: Fri Jul 20, 2012 01:49 AM
Re: Classic Outlook API vs Outlook 365 API
Posted: Thu Mar 05, 2026 04:37 PM

Offic365 API:

/fivewin/samples/outlook/testoutlook.prg

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

Regards,

Lailton Fernando Mariano
Posts: 2706
Joined: Fri Oct 07, 2005 01:50 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Thu Mar 05, 2026 06:09 PM

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

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Thu Mar 05, 2026 08:24 PM

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.

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 2706
Joined: Fri Oct 07, 2005 01:50 PM
Re: Classic Outlook API vs Outlook 365 API
Posted: Thu Mar 05, 2026 09:14 PM

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