diff --git a/CHANGELOG.md b/CHANGELOG.md index ce78df9..d3aeb75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ Notable changes to Mailpit will be documented in this file. +## 0.0.8 + +### Bugfix +- Fix total/unread count after failed message inserts + +### UI +- Add project links to help in CLI + + ## 0.0.7 ### Bugfix diff --git a/README-BUILDING.md b/README-BUILDING.md deleted file mode 100644 index 7ceb95b..0000000 --- a/README-BUILDING.md +++ /dev/null @@ -1,45 +0,0 @@ -# Building Mailpit from source - -Go (>= version 1.8) and npm are required to compile mailpit from source. - -``` -git clone git@github.com:axllent/mailpit.git -cd mailpit -``` - -## Building the UI - -The Mailpit web user interface is built with node. In the project's root (top) directory run the following to install the required node modules: - - -### Installing the node modules -``` -npm install -``` - - -### Building the web UI - -``` -npm run build -``` - -You can also run `npm run watch` which will watch for changes and rebuild the HTML/CSS/JS automatically when changes are detected. -Please note that you must restart Mailpit (`go run .`) to run with the changes. - - -## Build the mailpit binary - -One you have the assets compiled, you can build mailpit as follows: -``` -go build -ldflags "-s -w" -``` - -## Building a stand-alone sendmail binary - -This step is unnecessary, however if you do not intend to either symlink `sendmail` to mailpit or configure your existing sendmail to route mail to mailpit, you can optionally build a stand-alone sendmail binary. - -``` -cd sendmail -go build -ldflags "-s -w" -``` diff --git a/README.md b/README.md index c3d641e..2e9eef7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ It acts as both an SMTP server, and provides a web interface to view all capture Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster. +![Mailpit](https://raw.githubusercontent.com/axllent/mailpit/develop/screenshot.png) ## Features @@ -18,7 +19,7 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster. - Configurable automatic email pruning (default keeps the most recent 500 emails) - Fast SMTP processing & storing - approximately 300-600 emails per second depending on CPU, network speed & email size - Can handle tens of thousands of emails -- Multi-arch [Docker images](https://github.com/axllent/mailpit/wiki/Docker-image) +- Multi-arch [Docker images](https://github.com/axllent/mailpit/wiki/Docker-images) ## Planned features @@ -31,7 +32,7 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster. Download a pre-built binary in the [releases](https://github.com/axllent/mailpit/releases/latest). The `mailpit` can be placed in your `$PATH`, or simply run as `./mailpit`. See `mailpit -h` for options. -To build mailpit from source see [building from source](README-BUILDING.md). +To build Mailpit from source see [building from source](https://github.com/axllent/mailpit/wiki/Building-from-source). ### Configuring sendmail @@ -43,11 +44,11 @@ You can use `mailpit sendmail` as your sendmail configuration in `php.ini`: sendmail_path = /usr/local/bin/mailpit sendmail ``` -If mailpit is found on the same host as sendmail, you can symlink the mailpit binary to sendmail, eg: `ln -s /usr/local/bin/mailpit /usr/sbin/sendmail` (only if mailpit is running on default 1025 port). +If Mailpit is found on the same host as sendmail, you can symlink the Mailpit binary to sendmail, eg: `ln -s /usr/local/bin/mailpit /usr/sbin/sendmail` (only if Mailpit is running on default 1025 port). You can use your default system `sendmail` binary to route directly to port `1025` (configurable) by calling `/usr/sbin/sendmail -S localhost:1025`. -You can build a mailpit-specific sendmail binary from source ( see [building from source](README-BUILDING.md)). +You can build a Mailpit-specific sendmail binary from source ( see [building from source](https://github.com/axllent/mailpit/wiki/Building-from-source)). ## Why rewrite MailHog? diff --git a/cmd/root.go b/cmd/root.go index 131030d..873b14c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,7 +20,11 @@ var rootCmd = &cobra.Command{ Short: "Mailpit is an email testing tool for developers", Long: `Mailpit is an email testing tool for developers. -It acts as an SMTP server, and provides a web interface to view all captured emails.`, +It acts as an SMTP server, and provides a web interface to view all captured emails. + +Documentation: + https://github.com/axllent/mailpit + https://github.com/axllent/mailpit/wiki`, Run: func(_ *cobra.Command, _ []string) { if err := config.VerifyConfig(); err != nil { logger.Log().Error(err.Error()) @@ -60,9 +64,12 @@ func SendmailExecute() { func init() { // hide autocompletion rootCmd.CompletionOptions.HiddenDefaultCmd = true - // rootCmd.Flags().SortFlags = false - // hide help + rootCmd.Flags().SortFlags = false + // hide help command rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) + // hide help flag + rootCmd.PersistentFlags().BoolP("help", "h", false, "This help") + rootCmd.PersistentFlags().Lookup("help").Hidden = true // defaults from envars if provided if len(os.Getenv("MP_DATA_DIR")) > 0 { @@ -84,7 +91,7 @@ func init() { rootCmd.Flags().StringVarP(&config.DataDir, "data", "d", config.DataDir, "Optional path to store peristent data") rootCmd.Flags().StringVarP(&config.SMTPListen, "smtp", "s", config.SMTPListen, "SMTP bind interface and port") rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI") - rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages per mailbox") - rootCmd.Flags().StringVarP(&config.AuthFile, "auth-file", "a", config.AuthFile, "A username:bcryptpw mapping file") + rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages to store") + rootCmd.Flags().StringVarP(&config.AuthFile, "auth-file", "a", config.AuthFile, "A password file for authentication (see wiki)") rootCmd.Flags().BoolVarP(&config.VerboseLogging, "verbose", "v", false, "Verbose logging") } diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..d2005ab Binary files /dev/null and b/screenshot.png differ diff --git a/smtpd/smtpd.go b/smtpd/smtpd.go index 00fbcea..3420167 100644 --- a/smtpd/smtpd.go +++ b/smtpd/smtpd.go @@ -4,6 +4,7 @@ import ( "bytes" "net" "net/mail" + "regexp" "github.com/axllent/mailpit/config" "github.com/axllent/mailpit/logger" @@ -19,7 +20,15 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error { } if _, err := storage.Store(storage.DefaultMailbox, data); err != nil { - logger.Log().Errorf("error storing message: %s", err.Error()) + // Value with size 4800709 exceeded 1048576 limit + re := regexp.MustCompile(`(Value with size \d+ exceeded \d+ limit)`) + tooLarge := re.FindStringSubmatch(err.Error()) + if len(tooLarge) > 0 { + logger.Log().Errorf("[db] error storing message: %s", tooLarge[0]) + } else { + logger.Log().Errorf("[db] error storing message") + logger.Log().Errorf(err.Error()) + } return err } diff --git a/storage/database.go b/storage/database.go index edc299a..d990205 100644 --- a/storage/database.go +++ b/storage/database.go @@ -209,6 +209,8 @@ func Store(mailbox string, b []byte) (string, error) { return "", err } + statsAddNewMessage(mailbox) + // save the raw email in a separate collection raw := clover.NewDocument() raw.Set("_id", id) @@ -218,12 +220,11 @@ func Store(mailbox string, b []byte) (string, error) { if err != nil { // delete the summary because the data insert failed logger.Log().Debugf("[db] error inserting raw message, rolling back") - _ = DeleteOneMessage(mailbox, id) + DeleteOneMessage(mailbox, id) + return "", err } - statsAddNewMessage(mailbox) - count++ if count%100 == 0 { logger.Log().Infof("100 messages added in %s", time.Since(per100start)) @@ -399,10 +400,7 @@ func GetMessage(mailbox, id string) (*data.Message, error) { from = &mail.Address{Name: env.GetHeader("From")} } - date, err := env.Date() - if err != nil { - // date = - } + date, _ := env.Date() obj := data.Message{ ID: q.ObjectId(), @@ -522,11 +520,18 @@ func UnreadMessage(mailbox, id string) error { // DeleteOneMessage will delete a single message from a mailbox func DeleteOneMessage(mailbox, id string) error { + q, err := db.FindById(mailbox, id) + if err != nil { + return err + } + + unreadStatus := !q.Get("Read").(bool) + if err := db.DeleteById(mailbox, id); err != nil { return err } - statsDeleteOneMessage(mailbox) + statsDeleteOneMessage(mailbox, unreadStatus) return db.DeleteById(mailbox+"_data", id) } diff --git a/storage/stats.go b/storage/stats.go index 4c3fc57..37a8162 100644 --- a/storage/stats.go +++ b/storage/stats.go @@ -63,13 +63,23 @@ func statsAddNewMessage(mailbox string) { statsLock.Unlock() } -// Deleting one will always mean it was read -func statsDeleteOneMessage(mailbox string) { +// Delete one message from the totals. If the message was unread, +// then it will also deduct one from the Unread status. +func statsDeleteOneMessage(mailbox string, unread bool) { statsLock.Lock() s, ok := mailboxStats[mailbox] if ok { + // deduct from the totals + if s.Total > 0 { + s.Total = s.Total - 1 + } + // only deduct if the original was unread + if unread && s.Unread > 0 { + s.Unread = s.Unread - 1 + } + mailboxStats[mailbox] = data.MailboxStats{ - Total: s.Total - 1, + Total: s.Total, Unread: s.Unread, } }