This is the main dovel repository, it has the Go code to run dovel SMTP server.
Author: blmayer (bleemayer@gmail.com)
Date: Sat Jan 21 22:49:40 2023 -0300
Parent: 7444744
Fixed sending email
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/Makefile b/Makefile index 3f70abd..7d7b6cc 100644 --- a/Makefile +++ b/Makefile @@ -9,3 +9,6 @@ deploy: dovel deploy-site: index.html scp $^ zero:dovel.email/ + +deploy-pages: $(wildcard www/*) + scp $^ zero:blmayer.dev/www/mail
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/cmd/dovel/main.go b/cmd/dovel/main.go index dc74fd5..e1fa6c2 100644 --- a/cmd/dovel/main.go +++ b/cmd/dovel/main.go @@ -11,6 +11,7 @@ import ( "time" "blmayer.dev/x/dovel/config" + "blmayer.dev/x/dovel/interfaces" "blmayer.dev/x/dovel/interfaces/file" "blmayer.dev/x/dovel/interfaces/gwi" "github.com/emersion/go-smtp" @@ -68,14 +69,16 @@ func (s Session) Data(r io.Reader) error { } // get user from to field - for _, to := range email.To { - userDomain := strings.Split(to.Address, "@") + mail := interfaces.ToEmail(email) + for _, to := range mail.To { + userDomain := strings.Split(to, "@") handler, ok := s.handlers[userDomain[1]] if !ok { println("no handler for domain", userDomain[1]) return fmt.Errorf("no handler for domain %s", userDomain) } - if err := handler(email, content); err != nil { + mail.To = []string{to} + if err := handler(mail, content); err != nil { println("handler error", err.Error()) return fmt.Errorf("handler error %s", err.Error()) } @@ -126,7 +129,7 @@ func main() { switch hand.Handler { case "gwi": g := gwi.GWIConfig{Root: hand.Root} - g.Commands = map[string]func(email parsemail.Email, thread string) error{ + g.Commands = map[string]func(email interfaces.Email, thread string) error{ "close": g.Close, } b.handlers[hand.Domain] = g.GwiEmailHandler
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/config/config.go b/config/config.go index 3b7eb8b..191b27f 100644 --- a/config/config.go +++ b/config/config.go @@ -1,10 +1,10 @@ package config import ( - "github.com/vraycc/go-parsemail" + "blmayer.dev/x/dovel/interfaces" ) -type Handler func(email parsemail.Email, content []byte) error +type Handler func(email interfaces.Email, content []byte) error // Config is the configuration structure used to set up dovel // server and web interface. This should be a JSON file located
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/index.html b/index.html index 6978c2a..ad434c4 100644 --- a/index.html +++ b/index.html @@ -140,7 +140,7 @@ tt { <p> As a starting point, look at the <tt>www/</tt> folder in our git repo. They are templates that you can customize to your needs. Once you're - done simply restart the <tt>dovel-web</tt> application to apply + done simply restart the <tt>dovel</tt> application to apply changes. </p>
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/interfaces/file/file.go b/interfaces/file/file.go index d5a0300..731c4ed 100644 --- a/interfaces/file/file.go +++ b/interfaces/file/file.go @@ -13,6 +13,7 @@ import ( "os" "path" "strings" + "strconv" "time" "blmayer.dev/x/dovel/interfaces" @@ -25,16 +26,18 @@ type FileConfig struct { Root string Templates string Password string + Domain string } type FileHandler struct { templates *template.Template root string password string + domain string } func NewFileHandler(c FileConfig, fs map[string]any) (FileHandler, error) { - f := FileHandler{root: c.Root, password: c.Password} + f := FileHandler{root: c.Root, password: c.Password, domain: c.Domain} fs["inboxes"] = f.Mailboxes fs["mails"] = f.Mails fs["mail"] = f.Mail @@ -84,28 +87,55 @@ func (f FileHandler) SendHandler() http.HandlerFunc { http.Error(w, "form error"+err.Error(), http.StatusNotAcceptable) return } - f := r.MultipartForm + fo := r.MultipartForm body := bytes.Buffer{} form := multipart.NewWriter(&body) + email := interfaces.Email{ + From: fo.Value["from"][0], + To: []string{}, + Cc: []string{}, + Subject: fo.Value["subject"][0], + Date: time.Now(), + } + email.ID = fmt.Sprintf("%s%s", strconv.FormatInt(email.Date.Unix(), 10), email.From) // headers - body.WriteString("From: " + strings.Join(f.Value["from"], ", ") + "\r\n") - body.WriteString("To: " + strings.Join(append(f.Value["to"], f.Value["cco"]...), ", ") + "\r\n") - body.WriteString("Date: " + time.Now().Format(time.RFC1123Z) + "\r\n") - body.WriteString("Subject: " + f.Value["subject"][0] + "\r\n") - body.WriteString("Content-Type: multipart/mixed; boundary=" + form.Boundary() + "\r\n") + body.WriteString("MIME-Version: 1.0\r\n") + body.WriteString("From: " + email.From + "\r\n") + + for _, to := range fo.Value["to"] { + email.To = append(email.To, fmt.Sprintf("%s", to)) + } + body.WriteString("To: " + strings.Join(email.To, ", ") + "\r\n") + + if len(fo.Value["cc"]) > 0 && fo.Value["cc"][0] != "" { + for _, cc := range fo.Value["cc"] { + email.Cc = append(email.Cc, fmt.Sprintf("%s", cc)) + } + body.WriteString("Cc: " + strings.Join(email.Cc, ", ") + "\r\n") + } - text, err := form.CreatePart(map[string][]string{"Content-Type": []string{"text/plain"}}) + body.WriteString(fmt.Sprintf("Message-ID: <%s>\r\n", email.ID)) + + body.WriteString("Date: " + email.Date.Format(time.RFC1123Z) + "\r\n") + body.WriteString("Subject: " + email.Subject + "\r\n") + body.WriteString("Content-Type: multipart/mixed; boundary=" + form.Boundary() + "\r\n\r\n") + + text, err := form.CreatePart( + map[string][]string{ + "Content-Type": {"text/plain; charset=\"UTF-8\""}, + }, + ) if err != nil { - println("creatPart error: " + err.Error()) - http.Error(w, "creatPart error"+err.Error(), http.StatusInternalServerError) - return + println("creatPart error: " + err.Error()) + http.Error(w, "creatPart error"+err.Error(), http.StatusInternalServerError) + return } - text.Write([]byte(strings.Join(f.Value["body"], ""))) + text.Write([]byte(strings.Join(fo.Value["body"], ""))) - for _, fi := range f.File { - part, err := form.CreateFormFile(fi[0].Filename, fi[0].Filename) + for name, fi := range fo.File { + part, err := form.CreateFormFile(name, name) if err != nil { println("error creating form file: " + err.Error()) continue @@ -124,54 +154,77 @@ func (f FileHandler) SendHandler() http.HandlerFunc { } part.Write(content) } - email := body.String() + form.Close() // dns mx for email - addr := strings.Split(f.Value["to"][0], "@") - mxs, err := net.LookupMX(addr[1]) - if err != nil { - println("get MX error: " + err.Error()) - http.Error(w, "get MX error"+err.Error(), http.StatusInternalServerError) - return - } - if len(mxs) == 0 { - println("empty MX response") - http.Error(w, "empty MX reponse", http.StatusInternalServerError) - return - } + for _, to := range email.To { + addr := strings.Split(to, "@") + mxs, err := net.LookupMX(addr[1]) + if err != nil { + println("get MX error: " + err.Error()) + http.Error(w, "get MX error"+err.Error(), http.StatusInternalServerError) + return + } + if len(mxs) == 0 { + println("empty MX response") + http.Error(w, "empty MX reponse", http.StatusInternalServerError) + return + } - server := mxs[0].Host + ":smtp" - println("sending email to " + server + ":\n" + email) + server := mxs[0].Host + ":smtp" + err = smtp.SendMail( + server, + nil, + email.From, + []string{to}, + body.Bytes(), + ) + if err != nil { + println("sendMail error: " + err.Error()) + http.Error(w, "error sending email: "+err.Error(), http.StatusInternalServerError) + return + } - err = smtp.SendMail( - server, - nil, - f.Value["from"][0], - append(f.Value["to"], f.Value["cc"]...), - body.Bytes(), - ) - if err != nil { - println("sendMail error: " + err.Error()) - http.Error(w, "error sending email: "+err.Error(), http.StatusInternalServerError) - return + f.SaveSent(email, body.Bytes()) } http.Redirect(w, r, "/", http.StatusFound) } } -func (f FileHandler) Save(email parsemail.Email, content []byte) error { - to := strings.Split(email.To[0].Address, "@")[0] - mailDir := path.Join(f.root, to, email.Subject) +func (f FileHandler) Save(email interfaces.Email, content []byte) error { + mailDir := path.Join(f.root, email.To[0], email.Subject) + os.MkdirAll(mailDir, os.ModeDir|0o700) + + file, err := os.Create(path.Join(mailDir, email.ID)) + if err != nil { + println("file create", err.Error()) + return err + } + _, err = file.Write(content) + if err != nil { + println("file write", err.Error()) + return err + } + + return nil +} + +func (f FileHandler) SaveSent(email interfaces.Email, content []byte) error { + mailDir := path.Join(f.root, email.From, email.Subject) os.MkdirAll(mailDir, os.ModeDir|0o700) - file, err := os.Create(path.Join(mailDir, email.MessageID)) + file, err := os.Create(path.Join(mailDir, email.ID)) if err != nil { println("file create", err.Error()) return err } _, err = file.Write(content) + if err != nil { + println("file write", err.Error()) + return err + } - return err + return nil } func (f FileHandler) Mailboxes(folder string) ([]interfaces.Mailbox, error) { @@ -247,16 +300,20 @@ func (f FileHandler) Mail(file string) (interfaces.Email, error) { email := interfaces.Email{ ID: mail.MessageID, - To: mail.To[0].Address, From: mail.From[0].Address, Date: mail.Date, Subject: mail.Subject, Body: mail.TextBody, Attachments: map[string]interfaces.Attachment{}, } + for _, to := range mail.To { + email.To = append(email.To, to.Address) + } if len(mail.Cc) > 0 { - email.Cc = mail.Cc[0].Address + for _, cc := range mail.Cc { + email.Cc = append(email.Cc, cc.Address) + } } for _, a := range mail.Attachments {
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/interfaces/gwi/gwi.go b/interfaces/gwi/gwi.go index 9cc85f2..18545df 100644 --- a/interfaces/gwi/gwi.go +++ b/interfaces/gwi/gwi.go @@ -6,16 +6,16 @@ import ( "path" "strings" - "github.com/vraycc/go-parsemail" + "blmayer.dev/x/dovel/interfaces" ) type GWIConfig struct { Root string - Commands map[string]func(email parsemail.Email, thread string) error + Commands map[string]func(email interfaces.Email, thread string) error } -func (g GWIConfig) GwiEmailHandler(email parsemail.Email, content []byte) error { - userRepoDomain := strings.Split(email.To[0].Address, "@") +func (g GWIConfig) GwiEmailHandler(email interfaces.Email, content []byte) error { + userRepoDomain := strings.Split(email.To[0], "@") userRepo := strings.Split(userRepoDomain[0], "/") if len(userRepo) != 2 { println("received bad to", userRepo) @@ -46,7 +46,7 @@ func (g GWIConfig) GwiEmailHandler(email parsemail.Email, content []byte) error mailDir = path.Join(g.Root, user, repo, "mail/open", title) os.MkdirAll(mailDir, os.ModeDir|0o700) - mailFile, err := os.Create(path.Join(mailDir, email.MessageID)) + mailFile, err := os.Create(path.Join(mailDir, email.ID)) if err != nil { println("create mail file", err.Error()) return err @@ -64,7 +64,7 @@ func (g GWIConfig) GwiEmailHandler(email parsemail.Email, content []byte) error println("gwi applying commands") for com, f := range g.Commands { println("gwi applying", com) - if !strings.HasPrefix(email.TextBody, "!"+com) { + if !strings.HasPrefix(email.Body, "!"+com) { continue } if err := f(email, mailDir); err != nil { @@ -77,7 +77,7 @@ func (g GWIConfig) GwiEmailHandler(email parsemail.Email, content []byte) error return err } -func (g GWIConfig) Close(email parsemail.Email, threadPath string) error { +func (g GWIConfig) Close(email interfaces.Email, threadPath string) error { println("gwi closing thread", threadPath) // threadPath is like "/.../git/user/repo/mail/open/thread
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/interfaces/main.go b/interfaces/main.go index d3b297d..8c7c4e1 100644 --- a/interfaces/main.go +++ b/interfaces/main.go @@ -7,6 +7,7 @@ import ( "time" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/vraycc/go-parsemail" ) type User interface { @@ -43,10 +44,30 @@ type Attachment struct { type Email struct { ID string From string - To string - Cc string + To []string + Cc []string + BCc []string Date time.Time Subject string Body string Attachments map[string]Attachment } + +func ToEmail(mail parsemail.Email) Email { + m := Email{ + From: mail.Sender.Address, + To: []string{}, + Cc: []string{}, + Subject: mail.Subject, + ID: mail.MessageID, + Date: mail.Date, + Body: mail.TextBody, + } + for _, to := range mail.To { + m.To = append(m.To, to.Address) + } + for _, cc := range mail.Cc { + m.Cc = append(m.Cc, cc.Address) + } + return m +}
commit 8df1fb0050ef84d8d8a11089b521b012b206a6ad Author: blmayer <bleemayer@gmail.com> Date: Sat Jan 21 22:49:40 2023 -0300 Fixed sending email diff --git a/www/compose.html b/www/compose.html index 3ca1c02..0605c73 100644 --- a/www/compose.html +++ b/www/compose.html @@ -6,7 +6,7 @@ <pre> <b>// composing</b> <form action=/out method=post enctype=multipart/form-data> -// From: <input name=from placeholder="___________" {{if .inbox}}value="{{index .inbox 0}}@m.blmayer.dev"{{end}}> +// From: <input name=from placeholder="___________" {{if .inbox}}value="{{index .inbox 0}}"{{end}}> // To: <input name=to placeholder="________"> // CC: <input name=cc placeholder="________"> // CCo: <input name=cco placeholder="________">