Search code examples
gogmail-api

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


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/[email protected]/authhandler/authhandler.go:48 +0x5b
golang.org/x/oauth2.(*reuseTokenSource).Token(0xc00011d1e0)
        /home/joss/go/pkg/mod/golang.org/x/[email protected]/oauth2.go:304 +0xd5
golang.org/x/oauth2.(*Transport).RoundTrip(0xc00011d220, 0xc000124600)
        /home/joss/go/pkg/mod/golang.org/x/[email protected]/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/[email protected]/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/[email protected]/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/[email protected]/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
    }