Gmail API with Go and gmail.NewService invalid memory address or nil pointer dereference

Issue

I have an issue with the new NewService functionality of gmail api. If I use the deprecated gmail.New() everything works.
With NewService() I get invalid memory address or nil pointer dereference

My implementation is the following

type MailData struct {
    To      string
    Name    string
    Subject string
    Content template.HTML
}    

func doSend(msg *gmail.Message, srv *gmail.Service) error {
    _, err := srv.Users.Messages.Send("me", msg).Do()
    if err != nil {
        return err
    }
    return nil
}

func ComposeMessage(m models.MailData) *gmail.Message {

    var gmailMessage gmail.Message

    from := mail.Address{Name: "Sender", Address: os.Getenv("MAIL_FROM")}
    replyTo := os.Getenv("MAIL_REPLYTO")
    to := mail.Address{Name: m.Name, Address: m.To}

    header := make(map[string]string)
    header["From"] = from.String()
    header["Reply-To"] = replyTo
    header["To"] = to.String()
    header["Subject"] = m.Subject
    header["MIME-Version"] = "1.0"
    header["Content-Type"] = "text/html; 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" + string(m.Content)

    gmailMessage.Raw = base64.RawURLEncoding.EncodeToString([]byte(msg))
    return &gmailMessage
}

Using the old gmail.New() works, but it points out that the function is deprecated, so I need to change it to the new gmail.NewService. Though implementing it like below it doesn’t work

func sendGMail(m models.MailData) error {
    credentials := "../gmail_credentials.json"

    ctx := context.Background()
    srv, err := gmail.NewService(
        ctx,
        option.WithCredentialsFile(credentials),
        option.WithScopes("https://www.googleapis.com/auth/gmail.send"),
    )
    if err != nil {
        return errors.New(fmt.Sprintf("unable to retrieve gmail client: %s", err))
    }

    // Create message
    gMessage := ComposeMessage(m)

    if err := doSend(gMessage, srv); err != nil {
        return errors.New(fmt.Sprintf("could not send mail: %s", err))
    }
    fmt.Println("Email sent")

    return nil
}

Edit: the error I get is

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x9a161b]

goroutine 13 [running]:
golang.org/x/oauth2/authhandler.authHandlerSource.Token({{0xc2fb30, 0xc00003c108}, 0xc0002a1340, 0x0, {0x0, 0x0}})
        /home/joss/go/pkg/mod/golang.org/x/oauth2@v0.0.0-20211104180415-d3ed0bb246c8/authhandler/authhandler.go:48 +0x5b
golang.org/x/oauth2.(*reuseTokenSource).Token(0xc00011d1e0)
        /home/joss/go/pkg/mod/golang.org/x/oauth2@v0.0.0-20211104180415-d3ed0bb246c8/oauth2.go:304 +0xd5
golang.org/x/oauth2.(*Transport).RoundTrip(0xc00011d220, 0xc000124600)
        /home/joss/go/pkg/mod/golang.org/x/oauth2@v0.0.0-20211104180415-d3ed0bb246c8/transport.go:45 +0xa7
net/http.send(0xc000124600, {0xc1d200, 0xc00011d220}, {0xb13600, 0xc000263701, 0x0})
        /usr/local/go/src/net/http/client.go:252 +0x5d8
net/http.(*Client).send(0xc000483200, 0xc000124600, {0xc0002637f8, 0x4f49b5, 0x0})
        /usr/local/go/src/net/http/client.go:176 +0x9b
net/http.(*Client).do(0xc000483200, 0xc000124600)
        /usr/local/go/src/net/http/client.go:725 +0x908
net/http.(*Client).Do(...)
        /usr/local/go/src/net/http/client.go:593
google.golang.org/api/internal/gensupport.SendRequest({0x0, 0x0}, 0xb33a63, 0xc000124600)
        /home/joss/go/pkg/mod/google.golang.org/api@v0.30.0/internal/gensupport/send.go:43 +0xb8
google.golang.org/api/gmail/v1.(*UsersMessagesSendCall).doRequest(0xc000263e10, {0xb2b9fa, 0x4})
        /home/joss/go/pkg/mod/google.golang.org/api@v0.30.0/gmail/v1/gmail-gen.go:6836 +0xa05
google.golang.org/api/gmail/v1.(*UsersMessagesSendCall).Do(0xc000263e10, {0x0, 0x1b, 0xb4e56a})
        /home/joss/go/pkg/mod/google.golang.org/api@v0.30.0/gmail/v1/gmail-gen.go:6848 +0x78
github.com/user/mailprj/internal.doSend(0xc0000f6180, 0x12)
        /home/joss/user/mailprj/internal/gmail-api.go:159 +0xa5
github.com/user/mailprj/internal.sendGMail({{0xb3cd52, 0x12}, {0xb2b38e, 0x4}, {0xb4e56a, 0x29}, {0xc000610000, 0x8a66}})
        /home/joss/user/mailprj/internal/gmail-api.go:149 +0x1b2
github.com/user/mailprj/internal.ListenForGMail.func1()
        /home/joss/user/mailprj/internal/gmail-api.go:114 +0xc6
created by github.com/user/mailprj/internal.ListenForGMail
        /home/joss/user/mailprj/internal/gmail-api.go:111 +0x25
exit status 2

Solution

The issue was not that obvious but easy to solve.
While initializing the gmail.NewService() I needed to pass the config parameter as option, just like the previous implementation of gmail.New() was using

So before was

client := getClient(config)
srv, err := gmail.New(client)

And now it is

client := getClient(config)
ctx := context.Background()
srv, err := gmail.NewService(
    ctx,
    option.WithHTTPClient(client),
)

In more context of the Gmail API implementation for Go, you need to take all the functions used at

https://developers.google.com/gmail/api/quickstart/go or

https://github.com/googleworkspace/go-samples/blob/master/gmail/quickstart/quickstart.go

and put them in your go file. These will generate both an AccessToken and the Credentials which, if you follow the guide correctly, you will need to save as a json file and reuse. credentials.json are used to update your AccessToken.

Now instead of using gmail.New(), the correct way is to use the function as shown above.

The entire sendGmail function is as follows

func sendGMail(m models.MailData) error {
    credentials := "../gmail_credentials.json"
    b, err := ioutil.ReadFile(credentials)
    if err != nil {
        return errors.New(fmt.Sprint("unable to read credentials file:", err))
    }

    config, err := google.ConfigFromJSON(b, gmail.GmailSendScope)
    if err != nil {
        return errors.New(fmt.Sprint("unable to parse credentials file config:", err))
    }

    client := getClient(config)
    ctx := context.Background()
    srv, err := gmail.NewService(
        ctx,
        option.WithHTTPClient(client),
        option.WithScopes("https://www.googleapis.com/auth/gmail.send"),
    )
    if err != nil {
        return fmt.Errorf("unable to retrieve gmail client: %s", err)
    }

    // Create message
    gMessage := ComposeMessage(m)

    if err := doSend(gMessage, srv); err != nil {
        return fmt.Errorf("could not send mail: %s", err)
    }
    fmt.Println("Email sent")

    return nil
}

Answered By – Yiannis

Answer Checked By – Gilberto Lyons (GoLangFix Admin)

Leave a Reply

Your email address will not be published.