mirror of
https://github.com/neilalexander/yggmail.git
synced 2026-04-02 00:12:09 +03:00
Onboarding, Sent box and Outbox (#45)
* main - Set * Working * Welcome - Added welcome message generation * Smtpsender - On successful SMTP send create the "Sent" box and then try move from "Outbox" to "Sent" * Sent box - Create the mailbox in `main.go` and not every time we try move from `Outbox` to `Sent` * Use logegr * USer - Added logger pointer (and made use of it) - Disallow renaming or deletion of 'Sent' * When creating a new user set it up with logger * Encoded message * Added tests * Send a welcome mail on startup (soon to mke it only happen once) * try set flags * Onboarding flag set * Sender - Removed testing code * Welcome - Moved welcomer code * Cleaned up * Added more * renamed package * Removed comment * welcome - FIxed variable names * welcome - Removed semi-colons - Fixed imports * welcome - Ran `gofmt` * welcome test - Fixed up * h * main - Ran `gofmt` * Main - Fxied * Welcome - Foxed name * Added `.gitignore` * Mailbox - Disabled print logging * Fixed * fixedg * fixe and use `%v`
This commit is contained in:
committed by
GitHub
parent
fa32249f2f
commit
8bf3ba5f47
1
cmd/yggmail/.gitignore
vendored
Normal file
1
cmd/yggmail/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
yggmail
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
109
internal/welcome/welcome.go
Normal file
109
internal/welcome/welcome.go
Normal file
@@ -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 <b>%s</b>!
|
||||
|
||||
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 <a href="%s">website</a>
|
||||
|
||||
Thinking of contributing; we'd be more than happy
|
||||
to work together. Our project is hosted on <a href="%s">GitHub</a>.
|
||||
`
|
||||
|
||||
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
|
||||
}
|
||||
21
internal/welcome/welcome_test.go
Normal file
21
internal/welcome/welcome_test.go
Normal file
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user