diff --git a/cmd/yggmail/.gitignore b/cmd/yggmail/.gitignore
new file mode 100644
index 0000000..e3c3b17
--- /dev/null
+++ b/cmd/yggmail/.gitignore
@@ -0,0 +1 @@
+yggmail
diff --git a/cmd/yggmail/main.go b/cmd/yggmail/main.go
index 304c0cd..f84bcc7 100644
--- a/cmd/yggmail/main.go
+++ b/cmd/yggmail/main.go
@@ -32,6 +32,7 @@ import (
"github.com/neilalexander/yggmail/internal/storage/sqlite3"
"github.com/neilalexander/yggmail/internal/transport"
"github.com/neilalexander/yggmail/internal/utils"
+ "github.com/neilalexander/yggmail/internal/welcome"
"golang.org/x/crypto/bcrypt"
)
@@ -102,14 +103,18 @@ func main() {
copy(sk, skBytes)
}
pk := sk.Public().(ed25519.PublicKey)
- log.Printf("Mail address: %s@%s\n", hex.EncodeToString(pk), utils.Domain)
+ mailAddrUser := hex.EncodeToString(pk)
+ mailAddr := fmt.Sprintf("%s@%s", mailAddrUser, utils.Domain)
+ log.Printf("Mail address: %s\n", mailAddr)
- for _, name := range []string{"INBOX", "Outbox"} {
+ for _, name := range []string{"INBOX", "Outbox", "Sent"} {
if err := storage.MailboxCreate(name); err != nil {
panic(err)
}
}
+ welcome.Onboard(mailAddrUser, storage, log)
+
switch {
case password != nil && *password:
log.Println("Please enter your new password:")
@@ -144,7 +149,6 @@ func main() {
log.Println("Password for IMAP and SMTP has been updated!")
os.Exit(0)
-
case passwordhash != nil && *passwordhash != "":
var hash = strings.TrimSpace(*passwordhash)
if len(hash) == 0 {
diff --git a/internal/imapserver/backend.go b/internal/imapserver/backend.go
index 7745b3c..c39c386 100644
--- a/internal/imapserver/backend.go
+++ b/internal/imapserver/backend.go
@@ -48,6 +48,7 @@ func (b *Backend) Login(conn *imap.ConnInfo, username, password string) (backend
backend: b,
username: username,
conn: conn,
+ log: b.Log,
}
return user, nil
}
diff --git a/internal/imapserver/user.go b/internal/imapserver/user.go
index 2a1256f..29b0c77 100644
--- a/internal/imapserver/user.go
+++ b/internal/imapserver/user.go
@@ -12,6 +12,7 @@ import (
"encoding/hex"
"errors"
"fmt"
+ "log"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/backend"
@@ -21,6 +22,7 @@ type User struct {
backend *Backend
username string
conn *imap.ConnInfo
+ log *log.Logger
}
func (u *User) Username() string {
@@ -64,21 +66,35 @@ func (u *User) GetMailbox(name string) (mailbox backend.Mailbox, err error) {
}
func (u *User) CreateMailbox(name string) error {
- return u.backend.Storage.MailboxCreate(name)
+ u.log.Printf("Creating mailbox '%s'...\n", name)
+
+ if e := u.backend.Storage.MailboxCreate(name); e != nil {
+ u.log.Printf("Error creating mailbox '%s': %v\n", name, e);
+ return e;
+ }
+
+ u.log.Printf("Created mailbox '%s'\n", name);
+ return nil;
}
func (u *User) DeleteMailbox(name string) error {
switch name {
- case "INBOX", "Outbox":
+ case "INBOX", "Outbox", "Sent":
return errors.New("Cannot delete " + name)
default:
- return u.backend.Storage.MailboxDelete(name)
+ if e := u.backend.Storage.MailboxDelete(name); e != nil {
+ u.log.Printf("Error deleting mailbox '%s': %v\n", name, e)
+ return e;
+ } else {
+ u.log.Printf("Deleted mailbox '%s'\n", name)
+ return e;
+ }
}
}
func (u *User) RenameMailbox(existingName, newName string) error {
switch existingName {
- case "INBOX", "Outbox":
+ case "INBOX", "Outbox", "Sent":
return errors.New("Cannot rename " + existingName)
default:
return u.backend.Storage.MailboxRename(existingName, newName)
diff --git a/internal/smtpsender/sender.go b/internal/smtpsender/sender.go
index 40b32f6..e9cf573 100644
--- a/internal/smtpsender/sender.go
+++ b/internal/smtpsender/sender.go
@@ -58,6 +58,7 @@ func (qs *Queues) manager() {
func (qs *Queues) QueueFor(from string, rcpts []string, content []byte) error {
pid, err := qs.Storage.MailCreate("Outbox", content)
+
if err != nil {
return fmt.Errorf("q.queues.Storage.MailCreate: %w", err)
}
@@ -176,7 +177,8 @@ func (q *Queue) run() {
if remaining, err := q.queues.Storage.QueueSelectIsMessagePendingSend("Outbox", ref.ID); err != nil {
return fmt.Errorf("q.queues.Storage.QueueSelectIsMessagePendingSend: %w", err)
} else if !remaining {
- return q.queues.Storage.MailDelete("Outbox", ref.ID)
+ q.queues.Log.Printf("Moving mail with id '%d' from Outbox to Sent\n", ref.ID)
+ return q.queues.Storage.MailMove("Outbox", ref.ID, "Sent")
}
return nil
diff --git a/internal/welcome/welcome.go b/internal/welcome/welcome.go
new file mode 100644
index 0000000..c8fef60
--- /dev/null
+++ b/internal/welcome/welcome.go
@@ -0,0 +1,109 @@
+package welcome
+
+import (
+ "bytes"
+ "fmt"
+ "github.com/emersion/go-message"
+ "github.com/neilalexander/yggmail/internal/storage"
+ "log"
+)
+
+const (
+ WEBSITE_URL = "https://github.com/neilalexander/yggmail"
+ CODE_URL = "https://github.com/neilalexander/yggmail"
+)
+
+func Onboard(user string, storage storage.Storage, log *log.Logger) {
+ // Fetch onboarding status
+ if f, e := storage.ConfigGet("onboarding_done"); e == nil {
+
+ // If we haven't onboarded yet
+ if len(f) == 0 {
+ log.Printf("Performing onboarding...\n")
+
+ // takes in addr and output writer
+ welcomeMsg, e := welcomeMessageFor(user)
+ if e != nil {
+ log.Println("Failure to generate welcome message")
+ }
+ var welcomeId int
+ if id, e := storage.MailCreate("INBOX", welcomeMsg); e != nil {
+ log.Printf("Failed to store welcome message: %v\n", e)
+ panic("See above")
+ } else {
+ welcomeId = id
+ }
+
+ if storage.MailUpdateFlags("INBOX", welcomeId, false, false, false, false) != nil {
+ panic("Could not set flags on onboarding message")
+ }
+
+ // set flag to never do it again
+ if storage.ConfigSet("onboarding_done", "true") != nil {
+ panic("Error storing onboarding flag")
+ }
+
+ log.Printf("Onboarding done\n")
+ } else {
+ log.Printf("Onboarding not required\n")
+ }
+ } else {
+ panic("Error fetching onboarding status")
+ }
+
+}
+
+func welcomeMessageFor(yourYggMailAddr string) ([]byte, error) {
+ var hdr = welcomeTo(yourYggMailAddr)
+
+ var buff = bytes.NewBuffer([]byte{})
+
+ // writer writes to underlying writer (our buffer)
+ // but returns a writer just for the body part
+ // (it will encode header to underlying writer
+ // first)
+ msgWrt, e := message.CreateWriter(buff, hdr)
+ if e != nil {
+ return nil, e
+ }
+
+ var formattedBody = fmt.Sprintf(welcomeBody, yourYggMailAddr, WEBSITE_URL, CODE_URL)
+
+ if _, e := msgWrt.Write([]byte(formattedBody)); e != nil {
+ return nil, e
+ }
+ // var ent, e = message.New(hdr, body_rdr)
+
+ return buff.Bytes(), nil
+}
+
+var welcomeSubject = "Welcome to Yggmail!"
+var welcomeBody = `
+Hey %s!
+
+We'd like to welcome you to Yggmail!
+
+You're about to embark in both a revolution and an
+evolution as you know it. The revolution is that this
+mailing system uses the new and experimental Yggdrasil
+internet routing system, the evolution is that it's
+good old email as you know it.
+
+Want to learn more? See the website
+
+Thinking of contributing; we'd be more than happy
+to work together. Our project is hosted on GitHub.
+`
+
+func welcomeTo(yourYggMailAddr string) message.Header {
+ // header would be a nice preview of what to expect
+ // of the message
+ var welcomeHdr = message.Header{}
+ welcomeHdr.Add("From", "Yggmail Team")
+ welcomeHdr.Add("To", yourYggMailAddr+"@yggmail")
+ welcomeHdr.Add("Subject", welcomeSubject)
+ // FIXME: Add content-type entry here
+
+ fmt.Printf("Generated welcome mesg '%v'\n", welcomeHdr)
+ return welcomeHdr
+}
diff --git a/internal/welcome/welcome_test.go b/internal/welcome/welcome_test.go
new file mode 100644
index 0000000..2d7b7cb
--- /dev/null
+++ b/internal/welcome/welcome_test.go
@@ -0,0 +1,21 @@
+package welcome
+
+import (
+ "fmt"
+ "testing"
+)
+
+func Test_WelcomeGenerate(t *testing.T) {
+ newUser := "Tristan"
+
+ // generate welcome message header
+ bytesOut, e := welcomeMessageFor(newUser)
+
+ if e != nil {
+ t.Fail()
+ } else if len(bytesOut) == 0 {
+ t.Fail()
+ }
+
+ fmt.Printf("Out: %v\n", bytesOut)
+}