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 
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:
it is simple and easy to use
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