gobuffalo / buffalo

Rapid Web Development w/ Go
http://gobuffalo.io
MIT License
8.07k stars 573 forks source link

Add support for mail #94

Closed markbates closed 7 years ago

bscott commented 7 years ago

@markbates Is the idea here , for there to be a mail helper or are we looking for something that supports different smtp options including templates?

I'm thinking along the lines of the mail_to helper in Rails...

markbates commented 7 years ago

I'm thinking of being able to send emails with different smtp options, support templates, etc...

paganotoni commented 7 years ago

@markbates can i take this story ? i would love to work on it for the next release, WDYT ?

markbates commented 7 years ago

@apaganobeleno sure, go for it!

markbates commented 7 years ago

here's an implementation of something I have in an app:

package mailers

import (
    "path/filepath"
    "strconv"

    "github.com/gobuffalo/buffalo"
    "github.com/gobuffalo/envy"
    "github.com/gobuffalo/packr"
    "github.com/gobuffalo/plush"
    "github.com/gopherguides/web/models"
    "github.com/pkg/errors"
    "github.com/shurcooL/github_flavored_markdown"
    gomail "gopkg.in/gomail.v2"
)

var dialer *gomail.Dialer
var templateBox = packr.NewBox("../templates")

func init() {
    port, _ := strconv.Atoi(envy.Get("SMTP_PORT", "1025"))
    dialer = gomail.NewDialer(envy.Get("SMTP_HOST", "localhost"), port, envy.Get("SMTP_USER", ""), envy.Get("SMTP_PASSWORD", ""))
}

type Message struct {
    *gomail.Message
    dialer *gomail.Dialer
}

func (m *Message) SetSubject(f string) {
    m.Message.SetHeader("Subject", f)
}

func (m *Message) SetFrom(f string) {
    m.Message.SetHeader("From", f)
}

func (m *Message) SetTo(to ...string) {
    m.Message.SetHeader("To", to...)
}

func (m *Message) SetBody(ct string, path string, data map[string]interface{}) error {
    tmpl, err := templateBox.MustString(path)
    if err != nil {
        return errors.WithStack(err)
    }
    s, err := plush.Render(tmpl, plush.NewContextWith(data))
    if err != nil {
        return errors.WithStack(err)
    }
    if filepath.Ext(path) == ".md" {
        s = string(github_flavored_markdown.Markdown([]byte(s)))
    }
    m.Message.SetBody(ct, s)
    return nil
}

func (m Message) Deliver() error {
    return m.dialer.DialAndSend(m.Message)
}

func NewMessage() *Message {
    return &Message{
        dialer:  dialer,
        Message: gomail.NewMessage(),
    }
}

func NewContactMessage(c *models.Contact) *Message {
    m := NewMessage()
    m.SetFrom("info@gopherguides.com")
    m.SetTo("info@gopherguides.com")
    m.SetBody("text/html", "mail/new_contact.md", map[string]interface{}{
        "contact": c,
    })
    m.SetSubject("New Contact | Gopher Guides")
    return m
}

func NewTrainerOpportunityMessage(c *models.TrainerOpportunity) *Message {
    m := NewMessage()
    m.SetFrom("info@gopherguides.com")
    m.SetTo("info@gopherguides.com")
    m.SetBody("text/html", "mail/new_trainer_opportunity.md", map[string]interface{}{
        "trainerOpportunity": c,
    })
    m.SetSubject("New Trainer Opportunity | Gopher Guides")
    return m
}

it's not perfect, but it might give you some ideas as to what the API might look like.

paganotoni commented 7 years ago

Thats a good start @markbates ! Adding here what i'm thinking mail support should be inside buffalo:

are there other things i'm forgetting about ?

markbates commented 7 years ago

Those all sound correct to me. I'm sure we'll come up with other stuff as we move deeper into this.

paganotoni commented 7 years ago

@markbates i build this small Gist to show progress on this ticket https://gist.github.com/apaganobeleno/4bf9fc532857a4a36164627e8c3c6455, LMK your thoughts.

I have some "shape" questions for this implementation:

markbates commented 7 years ago

Cool. I'll have a look in the morning. But to answer your questions, I definitely think it should be in a separate package. perhaps the github.com/gobuffalo/x/ packages to start with. just a thought.

markbates commented 7 years ago

I'll review the gist tomorrow and outline a few of my own thoughts.

markbates commented 7 years ago

I think you need a proper message type, something like this:

type Message struct {
    From    string
    To      []string
    CC      []string
    Bcc     []string
    Subject string
    // ...
}

func (m *Message) AddBody(r render.Renderer, data render.Data) error {
    return nil
}

func (m *Message) AddAttachment(name, content_type string, r io.Reader) error {
    return nil
}
markbates commented 7 years ago

And an Deliverer interface:

type Deliverer interface {
    Deliver(Message) error
}
markbates commented 7 years ago

Then you can use it like this:

import "github.com/gobuffalo/x/mail/smtp"
var mailer Deliverer

func init() {
    mailer = smtp.New(//...)
}

func SendWelcomeEmail(data render.Data) error {
    m := NewMessage()
    m.From = "mark@example.com"
    // ...
    m.AddBody(r.HTML("welcom.html"), data)
    m.AddAttachment("foo.pdf", "application/pdf", myPDFReader)
    return mailer.Deliver(m)
}
markbates commented 7 years ago

I hope that all makes sense, I know it's pseudo-code and all.

paganotoni commented 7 years ago

@markbates should we then move this issue to be on the gobuffalo/x repo ?

markbates commented 7 years ago

Yeah, we should.