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="________">