diff --git a/api/helpers.go b/api/helpers.go
index 6e4839d46..b04e4878f 100644
--- a/api/helpers.go
+++ b/api/helpers.go
@@ -4,7 +4,9 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"net"
 	"net/http"
+	"net/http/httptrace"
 	"net/url"
 
 	"github.com/netlify/gotrue/conf"
@@ -12,6 +14,7 @@ import (
 	"github.com/netlify/gotrue/storage"
 	"github.com/pkg/errors"
 	"github.com/satori/go.uuid"
+	"github.com/sirupsen/logrus"
 )
 
 func addRequestID(globalConfig *conf.GlobalConfiguration) middlewareHandler {
@@ -107,3 +110,88 @@ func (a *API) getReferrer(r *http.Request) string {
 	}
 	return referrer
 }
+
+var privateIPBlocks []*net.IPNet
+
+func init() {
+	for _, cidr := range []string{
+		"127.0.0.0/8",    // IPv4 loopback
+		"10.0.0.0/8",     // RFC1918
+		"100.64.0.0/10",  // RFC6598
+		"172.16.0.0/12",  // RFC1918
+		"192.0.0.0/24",   // RFC6890
+		"192.168.0.0/16", // RFC1918
+		"169.254.0.0/16", // RFC3927
+		"::1/128",        // IPv6 loopback
+		"fe80::/10",      // IPv6 link-local
+		"fc00::/7",       // IPv6 unique local addr
+	} {
+		_, block, _ := net.ParseCIDR(cidr)
+		privateIPBlocks = append(privateIPBlocks, block)
+	}
+}
+
+func isPrivateIP(ip net.IP) bool {
+	for _, block := range privateIPBlocks {
+		if block.Contains(ip) {
+			return true
+		}
+	}
+	return false
+}
+
+type noLocalTransport struct {
+	inner  http.RoundTripper
+	errlog logrus.FieldLogger
+}
+
+func (no noLocalTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	ctx, cancel := context.WithCancel(req.Context())
+
+	ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
+		ConnectStart: func(network, addr string) {
+			fmt.Printf("Checking network %v\n", addr)
+			host, _, err := net.SplitHostPort(addr)
+			if err != nil {
+				cancel()
+				fmt.Printf("Canceleing dur to error in addr parsing %v", err)
+				return
+			}
+			ip := net.ParseIP(host)
+			if ip == nil {
+				cancel()
+				fmt.Printf("Canceleing dur to error in ip parsing %v", host)
+				return
+			}
+
+			if isPrivateIP(ip) {
+				cancel()
+				fmt.Println("Canceleing dur to private ip range")
+				return
+			}
+
+		},
+	})
+
+	req = req.WithContext(ctx)
+	return no.inner.RoundTrip(req)
+}
+
+func SafeRountripper(trans http.RoundTripper, log logrus.FieldLogger) http.RoundTripper {
+	if trans == nil {
+		trans = http.DefaultTransport
+	}
+
+	ret := &noLocalTransport{
+		inner:  trans,
+		errlog: log.WithField("transport", "local_blocker"),
+	}
+
+	return ret
+}
+
+func SafeHTTPClient(client *http.Client, log logrus.FieldLogger) *http.Client {
+	client.Transport = SafeRountripper(client.Transport, log)
+
+	return client
+}
diff --git a/api/hooks.go b/api/hooks.go
index 098126e4c..0554a8e08 100644
--- a/api/hooks.go
+++ b/api/hooks.go
@@ -66,16 +66,16 @@ func (w *Webhook) trigger() (io.ReadCloser, error) {
 		w.Retries = defaultHookRetries
 	}
 
-	client := http.Client{
-		Timeout: timeout,
-	}
-
 	hooklog := logrus.WithFields(logrus.Fields{
 		"component":   "webhook",
 		"url":         w.URL,
 		"signed":      w.jwtSecret != "",
 		"instance_id": w.instanceID,
 	})
+	client := http.Client{
+		Timeout: timeout,
+	}
+	client.Transport = SafeRountripper(client.Transport, hooklog)
 
 	for i := 0; i < w.Retries; i++ {
 		hooklog = hooklog.WithField("attempt", i+1)
diff --git a/mailer/mailer.go b/mailer/mailer.go
index 3c25f7407..c9071de05 100644
--- a/mailer/mailer.go
+++ b/mailer/mailer.go
@@ -2,10 +2,10 @@ package mailer
 
 import (
 	"net/url"
+	"regexp"
 
 	"github.com/netlify/gotrue/conf"
 	"github.com/netlify/gotrue/models"
-	"github.com/netlify/mailme"
 )
 
 // Mailer defines the interface a mailer must implement.
@@ -27,7 +27,7 @@ func NewMailer(instanceConfig *conf.Configuration) Mailer {
 	return &TemplateMailer{
 		SiteURL: instanceConfig.SiteURL,
 		Config:  instanceConfig,
-		Mailer: &mailme.Mailer{
+		Mailer: &MailmeMailer{
 			Host:    instanceConfig.SMTP.Host,
 			Port:    instanceConfig.SMTP.Port,
 			User:    instanceConfig.SMTP.User,
@@ -65,3 +65,9 @@ func getSiteURL(referrerURL, siteURL, filepath, fragment string) (string, error)
 	site.Fragment = fragment
 	return site.String(), nil
 }
+
+var urlRegexp = regexp.MustCompile(`^https?://[^/]+`)
+
+func enforceRelativeURL(url string) string {
+	return urlRegexp.ReplaceAllString(url, "")
+}
diff --git a/mailer/mailer_test.go b/mailer/mailer_test.go
index a24dc0880..bfedeaf70 100644
--- a/mailer/mailer_test.go
+++ b/mailer/mailer_test.go
@@ -29,3 +29,21 @@ func TestGetSiteURL(t *testing.T) {
 		assert.Equal(t, c.Expected, act)
 	}
 }
+
+func TestRelativeURL(t *testing.T) {
+	cases := []struct {
+		URL      string
+		Expected string
+	}{
+		{"https://test.example.com", ""},
+		{"http://test.example.com", ""},
+		{"test.example.com", "test.example.com"},
+		{"/some/path#fragment", "/some/path#fragment"},
+	}
+
+	for _, c := range cases {
+		res := enforceRelativeURL(c.URL)
+		assert.Equal(t, c.Expected, res, c.URL)
+	}
+}
+
diff --git a/mailer/mailme.go b/mailer/mailme.go
new file mode 100644
index 000000000..0a6ef5cbc
--- /dev/null
+++ b/mailer/mailme.go
@@ -0,0 +1,271 @@
+package mailer
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"html/template"
+	"io/ioutil"
+	"log"
+	"net"
+	"net/http"
+	"net/http/httptrace"
+	"strings"
+	"sync"
+	"time"
+
+	"gopkg.in/gomail.v2"
+
+	"github.com/sirupsen/logrus"
+)
+
+// TemplateRetries is the amount of time MailMe will try to fetch a URL before giving up
+const TemplateRetries = 3
+
+// TemplateExpiration is the time period that the template will be cached for
+const TemplateExpiration = 10 * time.Second
+
+// Mailer lets MailMe send templated mails
+type MailmeMailer struct {
+	From    string
+	Host    string
+	Port    int
+	User    string
+	Pass    string
+	BaseURL string
+	FuncMap template.FuncMap
+	cache   *TemplateCache
+}
+
+// Mail sends a templated mail. It will try to load the template from a URL, and
+// otherwise fall back to the default
+func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}) error {
+	if m.FuncMap == nil {
+		m.FuncMap = map[string]interface{}{}
+	}
+	if m.cache == nil {
+		m.cache = &TemplateCache{templates: map[string]*MailTemplate{}, funcMap: m.FuncMap}
+	}
+
+	tmp, err := template.New("Subject").Funcs(template.FuncMap(m.FuncMap)).Parse(subjectTemplate)
+	if err != nil {
+		return err
+	}
+
+	subject := &bytes.Buffer{}
+	err = tmp.Execute(subject, templateData)
+	if err != nil {
+		return err
+	}
+	body, err := m.MailBody(templateURL, defaultTemplate, templateData)
+	if err != nil {
+		return err
+	}
+
+	mail := gomail.NewMessage()
+	mail.SetHeader("From", m.From)
+	mail.SetHeader("To", to)
+	mail.SetHeader("Subject", subject.String())
+	mail.SetBody("text/html", body)
+
+	dial := gomail.NewPlainDialer(m.Host, m.Port, m.User, m.Pass)
+	return dial.DialAndSend(mail)
+
+}
+
+type MailTemplate struct {
+	tmp       *template.Template
+	expiresAt time.Time
+}
+
+type TemplateCache struct {
+	templates map[string]*MailTemplate
+	mutex     sync.Mutex
+	funcMap   template.FuncMap
+}
+
+func (t *TemplateCache) Get(url string) (*template.Template, error) {
+	cached, ok := t.templates[url]
+	if ok && (cached.expiresAt.Before(time.Now())) {
+		return cached.tmp, nil
+	}
+	data, err := t.fetchTemplate(url, TemplateRetries)
+	if err != nil {
+		return nil, err
+	}
+	return t.Set(url, data, TemplateExpiration)
+}
+
+func (t *TemplateCache) Set(key, value string, expirationTime time.Duration) (*template.Template, error) {
+	parsed, err := template.New(key).Funcs(t.funcMap).Parse(value)
+	if err != nil {
+		return nil, err
+	}
+
+	cached := &MailTemplate{
+		tmp:       parsed,
+		expiresAt: time.Now().Add(expirationTime),
+	}
+	t.mutex.Lock()
+	t.templates[key] = cached
+	t.mutex.Unlock()
+	return parsed, nil
+}
+
+func (t *TemplateCache) fetchTemplate(url string, triesLeft int) (string, error) {
+	client := &http.Client{}
+	client.Transport = SafeRountripper(client.Transport, logrus.New())
+
+	resp, err := client.Get(url)
+	if err != nil && triesLeft > 0 {
+		return t.fetchTemplate(url, triesLeft-1)
+	}
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode == 200 { // OK
+		bodyBytes, err := ioutil.ReadAll(resp.Body)
+		if err != nil && triesLeft > 0 {
+			return t.fetchTemplate(url, triesLeft-1)
+		}
+		if err != nil {
+			return "", err
+		}
+		return string(bodyBytes), err
+	}
+	if triesLeft > 0 {
+		return t.fetchTemplate(url, triesLeft-1)
+	}
+	return "", errors.New("Unable to fetch mail template")
+}
+
+func (m *MailmeMailer) MailBody(url string, defaultTemplate string, data map[string]interface{}) (string, error) {
+	if m.FuncMap == nil {
+		m.FuncMap = map[string]interface{}{}
+	}
+	if m.cache == nil {
+		m.cache = &TemplateCache{templates: map[string]*MailTemplate{}, funcMap: m.FuncMap}
+	}
+
+	var temp *template.Template
+	var err error
+
+	if url != "" {
+		var absoluteURL string
+		if strings.HasPrefix(url, "http") {
+			absoluteURL = url
+		} else {
+			absoluteURL = m.BaseURL + url
+		}
+		temp, err = m.cache.Get(absoluteURL)
+		if err != nil {
+			log.Printf("Error loading template from %v: %v\n", url, err)
+		}
+	}
+
+	if temp == nil {
+		cached, ok := m.cache.templates[url]
+		if ok {
+			temp = cached.tmp
+		} else {
+			temp, err = m.cache.Set(url, defaultTemplate, 0)
+			if err != nil {
+				return "", err
+			}
+		}
+	}
+
+	buf := &bytes.Buffer{}
+	err = temp.Execute(buf, data)
+	if err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+var privateIPBlocks []*net.IPNet
+
+func init() {
+	for _, cidr := range []string{
+		"127.0.0.0/8",    // IPv4 loopback
+		"10.0.0.0/8",     // RFC1918
+		"100.64.0.0/10",  // RFC6598
+		"172.16.0.0/12",  // RFC1918
+		"192.0.0.0/24",   // RFC6890
+		"192.168.0.0/16", // RFC1918
+		"169.254.0.0/16", // RFC3927
+		"::1/128",        // IPv6 loopback
+		"fe80::/10",      // IPv6 link-local
+		"fc00::/7",       // IPv6 unique local addr
+	} {
+		_, block, _ := net.ParseCIDR(cidr)
+		privateIPBlocks = append(privateIPBlocks, block)
+	}
+}
+
+func isPrivateIP(ip net.IP) bool {
+	for _, block := range privateIPBlocks {
+		if block.Contains(ip) {
+			return true
+		}
+	}
+	return false
+}
+
+type noLocalTransport struct {
+	inner  http.RoundTripper
+	errlog logrus.FieldLogger
+}
+
+func (no noLocalTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	ctx, cancel := context.WithCancel(req.Context())
+
+	ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
+		ConnectStart: func(network, addr string) {
+			fmt.Printf("Checking network %v\n", addr)
+			host, _, err := net.SplitHostPort(addr)
+			if err != nil {
+				cancel()
+				fmt.Printf("Canceleing dur to error in addr parsing %v", err)
+				return
+			}
+			ip := net.ParseIP(host)
+			if ip == nil {
+				cancel()
+				fmt.Printf("Canceleing dur to error in ip parsing %v", host)
+				return
+			}
+
+			if isPrivateIP(ip) {
+				cancel()
+				fmt.Println("Canceleing dur to private ip range")
+				return
+			}
+
+		},
+	})
+
+	req = req.WithContext(ctx)
+	return no.inner.RoundTrip(req)
+}
+
+func SafeRountripper(trans http.RoundTripper, log logrus.FieldLogger) http.RoundTripper {
+	if trans == nil {
+		trans = http.DefaultTransport
+	}
+
+	ret := &noLocalTransport{
+		inner:  trans,
+		errlog: log.WithField("transport", "local_blocker"),
+	}
+
+	return ret
+}
+
+func SafeHTTPClient(client *http.Client, log logrus.FieldLogger) *http.Client {
+	client.Transport = SafeRountripper(client.Transport, log)
+
+	return client
+}
diff --git a/mailer/template.go b/mailer/template.go
index 3e7c9a2d7..da0964d0e 100644
--- a/mailer/template.go
+++ b/mailer/template.go
@@ -4,14 +4,13 @@ import (
 	"github.com/badoux/checkmail"
 	"github.com/netlify/gotrue/conf"
 	"github.com/netlify/gotrue/models"
-	"github.com/netlify/mailme"
 )
 
 // TemplateMailer will send mail and use templates from the site for easy mail styling
 type TemplateMailer struct {
 	SiteURL string
 	Config  *conf.Configuration
-	Mailer  *mailme.Mailer
+	Mailer  *MailmeMailer
 }
 
 const defaultInviteMail = `<h2>You have been invited</h2>
@@ -57,7 +56,7 @@ func (m *TemplateMailer) InviteMail(user *models.User, referrerURL string) error
 	return m.Mailer.Mail(
 		user.Email,
 		string(withDefault(m.Config.Mailer.Subjects.Invite, "You have been invited")),
-		m.Config.Mailer.Templates.Invite,
+		enforceRelativeURL(m.Config.Mailer.Templates.Invite),
 		defaultInviteMail,
 		data,
 	)
@@ -80,7 +79,7 @@ func (m *TemplateMailer) ConfirmationMail(user *models.User, referrerURL string)
 	return m.Mailer.Mail(
 		user.Email,
 		string(withDefault(m.Config.Mailer.Subjects.Confirmation, "Confirm Your Signup")),
-		m.Config.Mailer.Templates.Confirmation,
+		enforceRelativeURL(m.Config.Mailer.Templates.Confirmation),
 		defaultConfirmationMail,
 		data,
 	)
@@ -104,7 +103,7 @@ func (m *TemplateMailer) EmailChangeMail(user *models.User, referrerURL string)
 	return m.Mailer.Mail(
 		user.EmailChange,
 		string(withDefault(m.Config.Mailer.Subjects.EmailChange, "Confirm Email Change")),
-		m.Config.Mailer.Templates.EmailChange,
+		enforceRelativeURL(m.Config.Mailer.Templates.EmailChange),
 		defaultEmailChangeMail,
 		data,
 	)
@@ -127,7 +126,7 @@ func (m *TemplateMailer) RecoveryMail(user *models.User, referrerURL string) err
 	return m.Mailer.Mail(
 		user.Email,
 		string(withDefault(m.Config.Mailer.Subjects.Recovery, "Reset Your Password")),
-		m.Config.Mailer.Templates.Recovery,
+		enforceRelativeURL(m.Config.Mailer.Templates.Recovery),
 		defaultRecoveryMail,
 		data,
 	)