Jump to content
Ugochukwu Mmaduekwe

Sending Email via GMail Using OAuth 2.0 via Indy

Recommended Posts

3 minutes ago, rvk said:

I don't think TIdMessage creates a much different text but I'm not sure.

TIdMessage creates RFC822-style emails.  Do note, however, that the TIdMessage.SaveTo...() methods are designed for SMTP usage, and so will escape emails per SMTP dot-transparency guidelines.  So, to use TIdMessage emails in other protocols, be sure to use the overloaded SaveTo...() methods provided by the TIdMessageHelper unit, which adds an AUseDotTransparency parameter that you can set to False to disable that escaping.

 

https://www.indyproject.org/2015/07/30/new-tidmessage-helper/

Share this post


Link to post
On 12/14/2021 at 1:11 AM, JLG said:

the following is based on your existing code for getting token

 

function TgmailFrm.Sendemail(FromName, emailFrom, emailRecip, emailSubject,
  emailBody, emailAttach: string; Quietly: Boolean): Boolean;

I believe the problem with your code is you are not base64url encoding the message.  If you have Delphi 11, you could add the code fragment below to your code and I think it should work.

 

 

uses System.NetEncoding;

 

var

  MSEncoded : TStringStream;

begin

        MSEncoded := TStringStream.Create;

        IdMessage.SaveToStream(MS);

        TNetEncoding.Base64URL.Encode(MS, MSEncoded);

 

end;

 

Share this post


Link to post

in order to use the drafts folder of gmail, you need permission. gmail does not allow apps permission to drafts if all they need is to send emails. they insist to use https://gmail.googleapis.com/gmail/v1/users/' + emailFrom +/messages/send  instead.

if i had access to drafts, then I could use the smtp component. this works well, but the google team complained to me that they don't allow this as I only require to send email, so no access to smtp.

the code using synapse above is using smtp. when I experimented with smtp, I did not need JSON as you say correctly. problem is now that I am attempting to email directly, the code above wont work.

I tried adding the header accept json and mmime type as suggested, but to no avail...

gmail staff are incredibly unhelpful. they refuse to support delphi questions. they have sample code for other platforms(see https://developers.google.com/gmail/api/quickstart/js). I just cant get code working for delphi.

does anyone there have a simple send with attachment code using delphi for gmail without smtp but using REST?

Share this post


Link to post
1 minute ago, JLG said:

I tried adding the header accept json and mmime type as suggested, but to no avail...

Can you post the raw rfc822 data in a txt file here with a simple testmail? (Did you try the overloaded saveto helper to disable escaping email adresses as suggested??)

 

I can try it here to see what goes wrong.

The rfc822 mimetype should work with send-api too (i thought).

 

I also found that the error messages you get are indeed often unhelpfull (just a 4xx rejected without reason).

 

Another option is just plain smtp with a specific app-password. But that's something each user should set in their own account (and provide your app with that password). Using the authetication method is much easier. Downside is that your app needs to go through the approval process of Google for the specified access.

 

Share this post


Link to post
On 12/16/2021 at 10:06 PM, Geoffrey Smith said:

I believe the problem with your code is you are not base64url encoding the message.  If you have Delphi 11, you could add the code fragment below to your code and I think it should work.

 

 

uses System.NetEncoding;

 

var

  MSEncoded : TStringStream;

begin

        MSEncoded := TStringStream.Create;

        IdMessage.SaveToStream(MS);

        TNetEncoding.Base64URL.Encode(MS, MSEncoded);

 

end;

 

I am afraid I still get a "bad request" response

Share this post


Link to post

Btw. It's best you first try a very simple rfc822 style message without attachment. If that works you can debug why adding attachments doesn't work.

 

You can post the simple message here if it doesn't work for you so we can debug it. (i.e. wrong headers due to not disabling escaping mail adresses)

 

 

Share this post


Link to post
On 12/16/2021 at 4:35 PM, Remy Lebeau said:

TIdMessage creates RFC822-style emails.  Do note, however, that the TIdMessage.SaveTo...() methods are designed for SMTP usage, and so will escape emails per SMTP dot-transparency guidelines.  So, to use TIdMessage emails in other protocols, be sure to use the overloaded SaveTo...() methods provided by the TIdMessageHelper unit, which adds an AUseDotTransparency parameter that you can set to False to disable that escaping.

 

https://www.indyproject.org/2015/07/30/new-tidmessage-helper/

I did try what you said by doing this code IdMessage.SaveToStream(MS,False,false); this resolves the dottransparency. but still not working. I enclose the txt file of the MS saved as file (I just changed the email address to xxxx to protect privacy)

I do not know what is wrong, other than perhaps not the right json format for gmail ? I tried encoding base64... not resolved..

messsage.txt

Share this post


Link to post

I just tested your messsage.txt and it works for me. I can create a draft with that message but I can also directly send it (because I have both draft and send access in my app approval).


This is my code. (Although I would remove the headers From, Reply-To and Date because those would be added automatically. It doesn't hurt when you add them but they are NOT used. From is always the address of the default sender in gmail and Reply-To is always the gmail account and Date is always now.)

 

Quote

 

      Url := 'https://www.googleapis.com/upload/gmail/v1/users/' + gOAuth2.email + '/messages/send';
      Response := TStringList.Create;
      HTTP := THTTPSend.Create;
      try
        // WorkStr := CreatePlainEML;
        WorkStr := TFile.ReadAllText('C:\Temp\messsage.txt');

        WriteStrToStream(HTTP.Document, ansistring(WorkStr));

        json := nil;

        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
            Result := SUCCESS_SUCCESS;
          end;
        end;

      finally
        HTTP.Free;
        Response.Free;
      end;

 

 

Looking at your code I now do see a difference with my code.

You are using Url := 'https://gmail.googleapis.com/gmail/v1/users/' + emailFrom + '/messages/send';

I'm using Url := 'https://www.googleapis.com/upload/gmail/v1/users/' + gOAuth2.email + '/messages/send';

 

You need to use the Upload URI, for media upload requests to post this as raw message.

See here https://developers.google.com/gmail/api/reference/rest/v1/users.messages/send

There is a difference with /gmail/v1 and /upload/gmail/v1.

 

If I use your Url I also get an invalid JSON error message 400.

So use the other Url and it should be fine.

 

Edited by rvk

Share this post


Link to post
On 12/18/2021 at 8:26 PM, rvk said:

I just tested your messsage.txt and it works for me. I can create a draft with that message but I can also directly send it (because I have both draft and send access in my app approval).


This is my code. (Although I would remove the headers From, Reply-To and Date because those would be added automatically. It doesn't hurt when you add them but they are NOT used. From is always the address of the default sender in gmail and Reply-To is always the gmail account and Date is always now.)

 

 

Looking at your code I now do see a difference with my code.

You are using Url := 'https://gmail.googleapis.com/gmail/v1/users/' + emailFrom + '/messages/send';

I'm using Url := 'https://www.googleapis.com/upload/gmail/v1/users/' + gOAuth2.email + '/messages/send';

 

You need to use the Upload URI, for media upload requests to post this as raw message.

See here https://developers.google.com/gmail/api/reference/rest/v1/users.messages/send

There is a difference with /gmail/v1 and /upload/gmail/v1.

 

If I use your Url I also get an invalid JSON error message 400.

So use the other Url and it should be fine.

 

thanks a million.

I now got it to work. the main points seem to be  'https://www.googleapis.com/upload/gmail/v1/users/' + gOAuth2.email + '/messages/send'; and  HTTP.Headers.Add('Accept: application/json');

it now works.

however when i try with an attachment - it does send, but the receiver does not get it as an attachment, but a load of base64 text within the email.

any further ideas?

I really appreciate your help.

Share this post


Link to post
4 minutes ago, JLG said:

thanks a million.

I now got it to work. the main points seem to be  'https://www.googleapis.com/upload/gmail/v1/users/' + gOAuth2.email + '/messages/send'; and  HTTP.Headers.Add('Accept: application/json');

it now works.

however when i try with an attachment - it does send, but the receiver does not get it as an attachment, but a load of base64 text within the email.

any further ideas?

I really appreciate your help.

I really sorted it now.

I needed the following line

IdMessage.ContentType := 'multipart/mixed';

 

phew.

thanks to all of you out there!!

 

Share this post


Link to post
21 hours ago, JLG said:

I really sorted it now.

I needed the following line

IdMessage.ContentType := 'multipart/mixed';

If you use the TIdMessageBuilder... classes, such as TIdMessageBuilderPlain or TIdMessageBuilderHtml (https://www.indyproject.org/2008/01/16/new-html-message-builder-class/), they will populate the TIdMessage for you.  If you populate the TIdMessage manually, then yes, you are responsible for populating it correctly (ie https://www.indyproject.org/2005/08/17/html-messages/).

Share this post


Link to post

Guys, the official way to interact with GMAIL is via OAuth authentication and JSON. That's what the documentation says.

 

The app password, 2FA or the IMAP settings are there but they depend on the user to allow and set up the parameters. 

 

 

 

Edited by John Kouraklis
  • Like 1

Share this post


Link to post
Posted (edited)

I ended up creating a TCustomAuthenticator descendent class for this. 

It implements a OAuth2 Authenticator:

- Allows authorization via externel browser

- Uses PKCE flow for added security

- Generates new tokens when they expire (using the refresh token)

 

In the repository there's a Demo app too.

 

Feel free to use it if you like:

delphi-google-oauth2 repo

Edited by imperyal
  • Like 2

Share this post


Link to post
Posted (edited)
On 12/21/2021 at 9:25 AM, John Kouraklis said:

Guys, the official way to interact with GMAIL is via OAuth authentication and JSON. That's what the documentation says.

FYI, this morning I checked in a new 'sasl-oauth' branch in Indy's repo, which includes a new 'IdSASLOAuth.pas' unit for SASL classes for OAUTH10A, OAUTHBEARER, and XOAUTH2 for TIdDICT, TIdPOP3, TIdSMTP, and TIdIMAP4.  They are still a work in progress (ie, no parsing of response JSON yet), and you are responsible for obtaining the OAuth tokens externally (ie, over HTTP), but once you have the tokens then you can use these SASLs to login to the DICT/POP3/SMTP/IMAP servers.

Edited by Remy Lebeau
  • Like 2
  • Thanks 3

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×