Ugochukwu Mmaduekwe 42 Posted November 7, 2019 Hi all. Does anyone have some sample code on how I can send email from Gmail in my Delphi App using OAuth 2.0 via Indy? Thanks. Share this post Link to post
Ugochukwu Mmaduekwe 42 Posted November 8, 2019 (edited) Any help please? @Remy Lebeau Edited November 8, 2019 by Ugochukwu Mmaduekwe Share this post Link to post
Geoffrey Smith 45 Posted November 8, 2019 Hi @Ugochukwu Mmaduekwe, Have a look at https://github.com/geoffsmith82/GmailAuthSMTP/ I just created a simple demo for you. You will need to get a client_id from google in their developer toolbox. Geoffrey 1 4 Share this post Link to post
Ugochukwu Mmaduekwe 42 Posted November 8, 2019 @Geoffrey Smith, thanks a lot for this. I really appreciate. Will test it once I get to my workstation. Thanks once again. Share this post Link to post
Remy Lebeau 1393 Posted November 11, 2019 (edited) On 11/7/2019 at 11:50 AM, Ugochukwu Mmaduekwe said: Does anyone have some sample code on how I can send email from Gmail in my Delphi App using OAuth 2.0 via Indy? Indy does not currently support OAuth yet. However, it would be fairly simple to create a TIdSASL-derived component that can be added to the TIdSMTP.SASLMechanisms collection to transmit an OAuth bearer token using the SMTP "AUTH XOAUTH2" command. But getting that token in the first place is the tricky part, and has to be done outside of SMTP. Edited November 11, 2019 by Remy Lebeau 1 2 Share this post Link to post
Remy Lebeau 1393 Posted November 13, 2019 (edited) On 11/11/2019 at 2:02 PM, Remy Lebeau said: Indy does not currently support OAuth yet. However, you don't actually need OAuth to access GMail. You can instead go into your Google account settings and generate an Application-specific password, which works just fine with Indy. Edited November 13, 2019 by Remy Lebeau Share this post Link to post
Geoffrey Smith 45 Posted November 13, 2019 On 11/12/2019 at 9:02 AM, Remy Lebeau said: Indy does not currently support OAuth yet. However, it would be fairly simple to create a TIdSASL-derived component that can be added to the TIdSMTP.SASLMechanisms collection to transmit an OAuth bearer token using the SMTP "AUTH XOAUTH2" command. But getting that token in the first place is the tricky part, and has to be done outside of SMTP. I have now updated my demo to use an TIdSASL derived component that I created. I must admit that it does use the Delphi TOAuth2Authenticator component as well which is not a Indy component... but it has been in Delphi going back quite a few versions. 1 Share this post Link to post
Ugochukwu Mmaduekwe 42 Posted November 13, 2019 (edited) 10 hours ago, Remy Lebeau said: However, you don't actually need OAuth to access GMail. You can instead go into your Google account settings and generate an Application-specific password, which works just fine with Indy. The problem with application password is that it requires 2FA setup which in turn forces you to authenticate each login even though it's for something as trivial as sending mails from your app. Edited November 13, 2019 by Ugochukwu Mmaduekwe 1 Share this post Link to post
Angus Robertson 574 Posted November 13, 2019 Gmail still allows SMTP and POP3 access with basic authentication, provided you ignore all attempts by Google to set-up better security on the account, and accept the odd/regular email that your account is being used by a suspicious application. But once you have turned on 'better security' (forget it's real name) you can not turn it off, so have to set-up a new gmail account. The OAuth2 option is not too bad, you only need to authenticate with a Google login using a browser once and the refresh token provided remains valid until not used for six months, or when the account is changed. so you can get a new access token each time you send email without needing to authenticate again. Other OAuth2 implementations usually expire the refresh token within 24 hours. Angus 1 Share this post Link to post
Remy Lebeau 1393 Posted November 13, 2019 5 hours ago, Ugochukwu Mmaduekwe said: The problem with application password is that it requires 2FA setup which in turn forces you to authenticate each login even though it's for something as trivial as sending mails from your app. 2FA is a good thing. And no, you don't actually need to authenticate every login. An app-specific password is meant to be used in only 1 location and shouldn't be passed around. You can set Google to remember where the password is being used from so you don't have to re-authenticate every time it is used from that location. I use app-specific passwords when testing Indy with GMail (POP3, SMTP, and IMAP) and don't have to re-authenticate each time. 1 Share this post Link to post
Geoffrey Smith 45 Posted December 17, 2019 I've updated my project so it now not only sends messages via gmail... but it can send hotmail.com/live.com/outlook.com emails. GmailAuthSMTP supports the XOAUTH2 and OAUTHBEARER authentication types and so could probably support other mail providers if they use those standards as well. https://github.com/geoffsmith82/GmailAuthSMTP/ 5 4 Share this post Link to post
LeusKapus 1 Posted January 29, 2020 Guys, just a heads up: Google is deprecating access to "unsecure apps" for Gmail for Business this February 15th. It is very likely that they'll extend this to all Gmail any time soon. 1 Share this post Link to post
rvk 33 Posted January 29, 2020 1 hour ago, LeusKapus said: Google is deprecating access to "unsecure apps" for Gmail for Business this February 15th. This should have no impact for application passwords. Your application doesn't even need to be changed. Just use the application password instead of the regular GMail password. Share this post Link to post
Geoffrey Smith 45 Posted June 2, 2020 I have updated the demo. The demo now includes saving and loading refresh tokens, as well as checking for expired access_tokens and refreshing them with the refresh_token. Have a look at https://github.com/geoffsmith82/GmailAuthSMTP/ Geoffrey 4 3 Share this post Link to post
EduPro 0 Posted June 30, 2020 @Geoffrey Smith, I downloaded your sample code but there is a file that did not come and that is dealt with in the uses which is Globals.pas Share this post Link to post
Geoffrey Smith 45 Posted June 30, 2020 18 minutes ago, EduPro said: @Geoffrey Smith, I downloaded your sample code but there is a file that did not come and that is dealt with in the uses which is Globals.pas You need to create that yourself. In this file you need to add the missing constant values to make the project compile. Share this post Link to post
MarkShark 27 Posted July 3, 2020 Geoffrey, thank you for this. I had no problems getting it working (with G Suite from a Delphi 10.4 app.) Nicely done! Share this post Link to post
PatV 1 Posted February 26, 2021 (edited) @Geoffrey Smith Thanks a lot for your sample on Github Patrick Edited February 26, 2021 by PatV Share this post Link to post
JLG 0 Posted December 12, 2021 On 11/8/2019 at 1:20 PM, Geoffrey Smith said: Hi @Ugochukwu Mmaduekwe, Have a look at https://github.com/geoffsmith82/GmailAuthSMTP/ I just created a simple demo for you. You will need to get a client_id from google in their developer toolbox. Geoffrey your gmailAuth on github is really good. but due to google strict requirements, I need a solution which does not use idsmtp but IDHTTP, as only have send authorization. I used your code to get the token - works brilliant. but I tried to make some code to post the message I generated using idmessage (into a tstreamstring) but always get a bad request error when posting. I think the problem is when doing IDHTTP.Post, the message is not formatted correctly. please could you advise? many thanks Share this post Link to post
Dave Nottage 557 Posted December 12, 2021 6 minutes ago, JLG said: due to google strict requirements, I need a solution which does not use idsmtp but IDHTTP Can you show which requirements you are working from? 7 minutes ago, JLG said: I generated using idmessage If what you are using is purely HTTP-based, then TIdMessage is unlikely to be appropriate. Please show your code. Share this post Link to post
JLG 0 Posted December 13, 2021 thanks foir your reply. the following is based on your existing code for getting token function TgmailFrm.Sendemail(FromName, emailFrom, emailRecip, emailSubject, emailBody, emailAttach: string; Quietly: Boolean): Boolean; var HTTP: TIdHTTP; Response: TStringList; Url, s: STring; Base64: TBase64Encoding; IdMessage: TIdMessage; MailBuilder: TIdMessageBuilderPlain; xoauthSASL: TIdSASLListEntry; MS: TStringStream; fIdSSLIOHandlerSocketOpenSSL: TIdSSLIOHandlerSocketOpenSSL; begin result := False; if not SetupAuthenticator or not HasSavedToken then begin /// Authenticate; this is done in setup authenticator IdHTTPServer1.Active := False; Exit; end; // if we only have refresh_token or access token has expired // request new access_token to use with request OAuth2_Enhanced.RefreshAccessTokenIfRequired; if OAuth2_Enhanced.AccessToken.Length = 0 then begin raise Exception.Create('Failed to authenticate properly'); // Exit; //need to free the stuff end; // xoauthSASL := IdSMTP1.SASLMechanisms.Add; // xoauthSASL.SASL := TIdOAuth2Bearer.Create(nil); // TIdOAuth2Bearer(xoauthSASL.SASL).Token := OAuth2_Enhanced.AccessToken; // TIdOAuth2Bearer(xoauthSASL.SASL).Host := IdSMTP1.Host; // TIdOAuth2Bearer(xoauthSASL.SASL).Port := IdSMTP1.Port; // TIdOAuth2Bearer(xoauthSASL.SASL).User := clientaccount; try Url := 'https://gmail.googleapis.com/gmail/v1/users/' + emailFrom + '/messages/send'; try HTTP := TIdHTTP.Create; fIdSSLIOHandlerSocketOpenSSL := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP); with fIdSSLIOHandlerSocketOpenSSL.SSLOptions do begin Method := sslvTLSv1_2; Mode := sslmClient; SSLVersions := [sslvTLSv1_2]; end; HTTP.IOHandler := fIdSSLIOHandlerSocketOpenSSL; // HTTP.Request.CharSet := 'utf-8'; IdMessage := TIdMessage.Create(Application); MailBuilder := TIdMessageBuilderPlain.Create; MailBuilder.PlainText.Text := emailBody; MailBuilder.PlainTextCharSet := 'iso-8859-1'; // MailBuilder.PlainTextContentTransfer := 'base64'; if (emailAttach <> '') and (FileExists(emailAttach)) then begin MailBuilder.Attachments.Add(emailAttach); IdMessage.ContentType := 'multipart/mixed'; end else IdMessage.ContentType := 'text/plain'; IdMessage := MailBuilder.NewMessage(); // if UseHTML then // 'text/html'; // else IdMessage.ContentType := 'text/plain'; IdMessage.Encoding := meMIME; IdMessage.From.Address := emailFrom; IdMessage.From.Name := FromName; IdMessage.ReplyTo.EMailAddresses := IdMessage.From.Address; IdMessage.Recipients.Add.Text := emailRecip; IdMessage.Subject := emailSubject; IdMessage.Body.Text := emailBody; IdMessage.NoEncode := False; MS := TStringStream.Create('', TEncoding.UTF8); // this enables me to show progress try if not Quietly then begin IdMessage.SaveToStream(MS); Sz := MS.Size; HTTP.onWork := EvHandler.DoWork; IdSMTP1.onWork := EvHandler.DoWork; HTTP.BeginWork(wmRead); // this enables me to show progress end; // if not quietly then with HTTP do begin Request.CustomHeaders.Add(Format('Authorization: Bearer %s', [OAuth2_Enhanced.AccessToken])); HandleRedirects := True; Response.KeepAlive := False; AllowCookies := True; end; MS.Position:=0; HTTP.Post(Url, MS); if not Quietly then ShowWait(0, 0, TimeToStr(Time) + ' email sent to ' + emailRecip, True); if not Quietly then HTTP.EndWork(wmRead); result := True; except On E: Exception do begin Add2Log('Error while sending email to ' + emailRecip + ': ' + E.Message); result := False; if not Quietly then ShowTimedMsg('Error while sending email to ' + emailRecip + ': ' + E.Message, 10); end; end; finally IdSMTP1.Disconnect; HTTP.Free; // fIdSSLIOHandlerSocketOpenSSL.Free; MS.Free; IdMessage.Free; MailBuilder.Free; end; finally IdHTTPServer1.Active := False; end; end; Share this post Link to post
Dave Nottage 557 Posted December 13, 2021 The docs for send say that the body (what you are passing in the content when you Post) should be an instance of Message, i.e. the JSON that is specified at that link. TIdMessage.SaveToStream does not create JSON in this format. You will need to either create code to do this, or find existing code that does. Share this post Link to post
JLG 0 Posted December 16, 2021 thanks. any idea how to do this. is there no way to convert idmessage to json? in the google docs it requires things like a unique message id and other stuff with no explanation of how. do i invent one or retrieve one?? their docs are so unhelpful to delphi user... Share this post Link to post
rvk 33 Posted December 16, 2021 (edited) For posting a draft message to GMail I don't need any JSON. I can just post the complete mail (eml-format) to https://www.googleapis.com/upload/gmail/v1/users/<email-adres>/drafts. You do need to set the header Authorization: Bearer with the correct access_token. Ps. My MimeType (with Synapse) is message/rfc822 and accept is application/json. I don't see that in your code. Small snippet from my code (as I said with Synapse). Quote Url := 'https://www.googleapis.com/upload/gmail/v1/users/' + gOAuth2.email + '/drafts'; WorkStr := CreatePlainEML; WriteStrToStream(HTTP.Document, ansistring(WorkStr)); HTTP.MimeType := 'message/rfc822'; HTTP.Headers.Clear; HTTP.Headers.Add('Authorization: Bearer ' + gOAuth2.access_token); HTTP.Headers.Add('Accept: application/json'); if HTTP.HTTPMethod('POST', Url) then begin Response.LoadFromStream(HTTP.Document); if HTTP.ResultCode <> 200 then ShowMessage(Response.Text) else begin obj := SO(Response.Text); Url := 'https://mail.google.com/mail?authuser=' + gOAuth2.email + '#drafts/' + urlencode(obj.S['message.id']); BrowseURL(Url); Result := SUCCESS_SUCCESS; end; end; The CreatePlainEML creates a mail message which is compatible with Outlook Express and Thunderbird (I believe this is the rfc822 e-mail standard). With TMimemess and multiple TMimepart parts followed by the TMememess.EncodeMessage-function from Synapse. So there shouldn't be any need for posting JSON (only retrieving the result as JSON). I don't think TIdMessage creates a much different text but I'm not sure. Edited December 16, 2021 by rvk 1 Share this post Link to post
Remy Lebeau 1393 Posted December 16, 2021 (edited) 2 hours ago, JLG said: is there no way to convert idmessage to json? No, unless you do it yourself manually. You are simply using the wrong tool for the job to begin with. TIdMessage is designed for email only, not for HTTP posts. There is nothing in Indy that specifically handles the Gmail REST API you are referring to, so you are going to have to implement it yourself. Edited December 16, 2021 by Remy Lebeau Share this post Link to post