Send a mail with a service account using Gmail API

1,954 views
Skip to first unread message

Vincent Jouglard

unread,
Jul 24, 2020, 12:12:47 PM7/24/20
to golang-nuts
Hi there,

I have been trying for quite some time to get over an error while trying to send an email using a service account, via the Gmail API.
I get:
googleapi: Error 400: Precondition check failed., failedPrecondition

Code is:
package gmailApi

import (
   "encoding/base64"
   "fmt"
   "net/mail"
   "strings"

)

func SendEmailVerification() {
   srv, err := gmail.NewService(context.Background(), option.WithCredentialsFile("credentials.json"), option.WithScopes("https://mail.google.com/", "https://www.googleapis.com/auth/gmail.modify", "https://www.googleapis.com/auth/gmail.compose", "https://www.googleapis.com/auth/gmail.send"))
   if err != nil {
       fmt.Println(err) // BUG(Vincent): TEST
   }

   // New message for our gmail service to send
   var message gmail.Message
   // Compose the message

// @ symbol modified for publishing on golang-nuts
   messageStr := []byte(
       "From: vincent[at]gamifly.gg\r\n" +
           "To: jouglardv[at]gmail.com\r\n" +
           "Subject: My first Gmail API message\r\n\r\n" +
           "Message body goes here!")

    // Place messageStr into message.Raw in base64 encoded format
   message.Raw = base64.URLEncoding.EncodeToString(messageStr)

    if rslt, err := srv.Users.GetProfile("me").Do(); err != nil {
       fmt.Println(err) // BUG(Vincent): TEST
   } else {
       fmt.Println("111", rslt)
   }

    msg := ComposeMessage()
   _, err = srv.Users.Messages.Send("me", msg).Do()
   if err != nil {
       fmt.Println(err) // BUG(Vincent): TEST
   } else {
       fmt.Println("OKOKOK")
   }
}

func ComposeMessage() *gmail.Message {
   from := mail.Address{"Vincent", "vincent[at]gamifly.gg"} // @ symbol modified for publishing on golang-nuts
   to := mail.Address{"Vincent", "jouglardv[at]gmail.com"} // @ symbol modified for publishing on golang-nuts

    header := make(map[string]string)
   header["From"] = from.String()
   header["To"] = to.String()
   header["Subject"] = encodeRFC2047("Hello, Gmail!")
   header["MIME-Version"] = "1.0"
   header["Content-Type"] = "text/plain; charset=\"utf-8\""
   header["Content-Transfer-Encoding"] = "base64"

    var msg string
   for k, v := range header {
       msg += fmt.Sprintf("%s: %s\r\n", k, v)
   }
   msg += "\r\n" + "Message"

    return &gmail.Message{
       Raw: base64.RawURLEncoding.EncodeToString([]byte(msg)),
   }
}

func encodeRFC2047(s string) string {
   // use mail's rfc2047 to encode any string
   addr := mail.Address{s, ""}
   return strings.Trim(addr.String(), " <>")
}


 
vincent[at]gamifly.gg is the GSuite user that is being impersonated.
jouglardv[at]gmail.com is a personnal address for testing purpose (plz don't spam <3 )

The service account has domain delegation activated, scopes that are passed are in API Controls > Domain-Wide Delegation: 
https://www.googleapis.com/auth/gmail.sendhttps://mail.google.com/https://www.googleapis.com/auth/gmail.modifyhttps://www.googleapis.com/auth/gmail.compose


Is there anything wrong that I may be doing ?
Thanks guys

Vincent Jouglard

unread,
Jul 24, 2020, 4:54:33 PM7/24/20
to golang-nuts
Hi John,

Thanks for your message.
The main difference with my implementation is that I use a service account which is authorized to send messages on my behalf...and I think that this is where lies the problem.

As i understand OAuth and the implementation from your exemple, a token is generated and could (will) need to be refreshed, requiring a human interaction (copy past a token from a UrL). Using a service account allows a full server-to-server flow.
Please correct me if I am mistaken!

Vincent Jouglard

unread,
Jul 27, 2020, 11:17:47 AM7/27/20
to golang-nuts
Hi John,

Thanks for you answer, which led me to the correct configuration.
It was all about the "subject" missing from my way of implementing the Service ; as the function gmail.New() is deprecated, gmail.NewService() is to be used instead...but it does not implement - yet - the "subject" prop.

A discussion is still opened to fix this lack of code: https://github.com/googleapis/google-api-go-client/issues/379
This describes exactly the code you sent me (btw, I don't know I received your answers by mail but they are not displayed here on groups.google.com)...which is working like a charm.

Thanks a lot again, 

Reply all
Reply to author
Forward
0 new messages