Attempting to Learn Go - Sending Email Via API Again

Mailing List

Previously on ATLG, we looked at how we can use Go templates to quickly use marked-up text. This time around we’re going to put those new formatting skills to good use! To do that we’ll be pulling in the mailgunner package we put together a few weeks ago. See I told you I had plans for it.

We’re going to jump right in and start building on top of what we already have so, you may want to take a moment to read the other two posts if you have not already.


Once again starting at the top! If you’ve been following the series so far you will no that for our examples we’re starting off with the current set of imports. You may notice one new standard library this time around errors, we’ll get to how we are using it in a moment. You may also note, that this time around we’ve trimmed out most of the unneeded struct parts and just left []Users with the name, username, and email entries. As I said last time that was really only for demonstration purposes on that post, so I’ve left it out.

You may also notice that our import from the mailgunner package, which currently doesn’t actually exist up on GitHub, is a little different. Having the leading c allows us to use the import as just that the letter “c” - so we can type out c.SomeFunc() instead of using client. Note that the mailgunner code will be tweaked slightly and put up on GitHub after the next post.

package main

import (
  "bytes"
  "encoding/json"
  "errors"
  "fmt"
  "io/ioutil"
  "net/http"
  "text/template"

  "github.com/shindakun/envy"
  c "github.com/shindakun/mailgunner/client"
)

const apiurl = "https://api.mailgun.net/v3/youremaildomain.com"

type Users []struct {
  Name     string `json:"name"`
  Username string `json:"username"`
  Email    string `json:"email"`
}

Now we’re down to our first real bit of new code. checkStatusCode() is a small utility (or helper) function which we’re using to check to see if the http response code is 200 or not. The endpoints we’re using should only ever respond to our code with a 200 OK if not we’re going to return an error. You might ask why we’re returning an error and not just panicking here? That’s because we want the code to panic and exit in the code block it’s called from, this way you don’t have to trace back. Later we’ll also look into gracefully handling errors instead of just panicking. Anyway, it is a good idea to always check response codes - we skipped over it in the previous examples but, I’ll try to remember doing it from here on out. Maybe we’ll start putting together a “utilities” package for stuff that can be reused.

func checkStatusCode(s int) error {
  if s != 200 {
    err := fmt.Sprintf("unexpected status code: %d", s)
    return errors.New(err)
  }
  return nil
}

sendEmail() is similar in some respects to the code we wrote to send our test message in the mailgunner post. This time, however, we’re passing in our bytes.Buffer which will hold our completed email template, and the email address. Look closely and you may notice that I’m swapping the “from” and “to” fields in mgc.FormatEmailRequest() this is so I can actually send the emails, but send them to a working email - please don’t try to email the example ones. They probably are all fake but still spamming isn’t polite.

func sendEmail(buf bytes.Buffer, email string) {
  mgKey, err := envy.Get("MGKEY")
  if err != nil {
    panic(err)
  }

  mgc := c.NewMgClient(apiurl, mgKey)

  req, err := mgc.FormatEmailRequest(email, "youremailaddress@",
    "Test email", buf.String())
  if err != nil {
    panic(err)
  }

  res, err := mgc.Client.Do(req)
  if err != nil {
    panic(err)
  }
  defer res.Body.Close()

Here we’re using our helper to check the returned status code and panic if we’re not looking good. Finally, as with every example so far, we’re printing our results to standard out.

  if err = checkStatusCode(res.StatusCode); err != nil {
    panic(err)
  }
  body, err := ioutil.ReadAll(res.Body)
  if err != nil {
    panic(err)
  }

  fmt.Println(string(body))
}

Alright, now lets dive into our main()! A lot of this will look familiar if you’ve read the previous posts. This iteration over the basics is what I’m hoping allows this to stick in my head, and maybe yours too!

func main() {
  APIURL := "https://jsonplaceholder.typicode.com/users"
  req, err := http.NewRequest(http.MethodGet, APIURL, nil)
  if err != nil {
    panic(err)
  }
  client := http.DefaultClient
  resp, err := client.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()

  if err = checkStatusCode(resp.StatusCode); err != nil {
    panic(err)
  }
  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    panic(err)
  }

  var u Users
  json.Unmarshal(body, &u)

Our message is pretty bland, with a full-featured mailing list app you’d make it easy to load custom messages, etc. But, as always, we’re just trying stuff out to learn. If we iterate on this enough it could easily become a full app one day. We’ll range through our users and pass the results of the populated template to sendEmail() which will do the actual sending.

  msgText := "Hi {{.Name}}! Or should I call you, {{.Username}}? There is a new post!\n\n"
  t := template.Must(template.New("msg").Parse(msgText))

  for _, v := range u {
    var buf bytes.Buffer

    err := t.Execute(&buf, v)
    if err != nil {
      panic(err)
    }
    sendEmail(buf, v.Email)
  }
}

And that’s it! Next time around we are going to tweak our mailgunner package a little bit and get it posted up on GitHub so we can actually go get it!


You can find the code for this and most of the other Attempting to Learn Go posts in the repo on GitHub.

{% github shindakun/atlg %}


Enjoy this post?
How about buying me a coffee?