├── .github └── logo.png ├── .gitignore ├── Makefile ├── README.md ├── after-install.sh ├── cmd └── mailway │ ├── cli.go │ ├── dkim.go │ ├── frontline.go │ ├── jwt.go │ ├── main.go │ ├── pem.go │ ├── preflight.go │ ├── recover.go │ ├── setup.go │ ├── supervisor.go │ └── update.go ├── conf.d ├── dkim.yml ├── logs.yml ├── mailway.yml ├── ports.yml └── spam.yml ├── frontline └── nginx.conf.tmpl ├── go.mod ├── go.sum ├── key.pub ├── spamc.py └── systemd ├── auth.service ├── forwarding.service ├── frontline.service ├── maildb.service ├── mailout.service ├── mailway-supervisor.service └── webhooks.service /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailway-app/mailway/fcd54f4b82d03fefc22ce90e4c4390680b30c398/.github/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | *.deb 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 1.0.0 2 | DIST = $(PWD)/dist 3 | FPM_ARGS = 4 | 5 | .PHONY: clean 6 | clean: 7 | rm -rf $(DIST) *.deb 8 | 9 | $(DIST)/mailway: 10 | mkdir -p $(DIST) 11 | cd ./cmd/mailway && \ 12 | go build -o $(DIST)/usr/local/sbin/mailway 13 | 14 | .PHONY: deb 15 | deb: $(DIST)/mailway 16 | mkdir -p \ 17 | $(DIST)/etc/mailway \ 18 | $(DIST)/etc/mailway/conf.d \ 19 | $(DIST)/etc/mailway/frontline \ 20 | $(DIST)/etc/systemd/system/ 21 | cp ./frontline/* $(DIST)/etc/mailway/frontline 22 | cp ./conf.d/* $(DIST)/etc/mailway/conf.d 23 | cp ./systemd/* $(DIST)/etc/systemd/system/ 24 | cp ./key.pub $(DIST)/etc/mailway 25 | cp ./spamc.py $(DIST)/usr/local/spamc.py 26 | chmod +x $(DIST)/usr/local/spamc.py 27 | fpm -n mailway -s dir -t deb --chdir=$(DIST) --version=$(VERSION) $(FPM_ARGS) \ 28 | --after-install ./after-install.sh \ 29 | --depends frontline \ 30 | --depends auth \ 31 | --depends forwarding \ 32 | --depends mailout \ 33 | --depends webhooks \ 34 | --depends maildb 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Mailway logo 3 |

4 | 5 | > A single **way** for all your **emails** 6 | 7 | Configure DNS on your domain, add rules for incoming emails, done. 8 | 9 | ## Self Host 10 | 11 | See [self-host] to run your own instance. 12 | 13 | [self-host]: http://docs.mailway.app/self-host/ 14 | -------------------------------------------------------------------------------- /after-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f /etc/mailway/conf.d/server-id.yml ]; then 4 | UUID=$(cat /proc/sys/kernel/random/uuid) 5 | echo "server_id: $UUID" > /etc/mailway/conf.d/server-id.yml 6 | echo "generated server id: $UUID" 7 | fi 8 | 9 | systemctl daemon-reload 10 | -------------------------------------------------------------------------------- /cmd/mailway/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | log "github.com/sirupsen/logrus" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | isLocalSetup bool 11 | 12 | rootCmd = &cobra.Command{ 13 | Use: "mailway", 14 | Short: "Mailway CLI", 15 | } 16 | setupCmd = &cobra.Command{ 17 | Use: "setup", 18 | Short: "Mailway instance setup", 19 | Args: cobra.NoArgs, 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | if err := setup(); err != nil { 22 | log.Fatal(err) 23 | } 24 | return nil 25 | }, 26 | } 27 | setupSecureSMTPCmd = &cobra.Command{ 28 | Use: "setup-secure-smtp", 29 | Short: "Mailway instance setup secure inbound SMTP", 30 | Args: cobra.NoArgs, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | if err := setupSecureSmtp(); err != nil { 33 | log.Fatal(err) 34 | } 35 | return nil 36 | }, 37 | } 38 | generateFrontlineConfigCmd = &cobra.Command{ 39 | Use: "reconfigure-frontline", 40 | Short: "Mailway instance generate the frontline NGINX configuration", 41 | Args: cobra.NoArgs, 42 | RunE: func(cmd *cobra.Command, args []string) error { 43 | if err := generateHTTPCert(); err != nil { 44 | log.Error(err) 45 | } 46 | if err := generateFrontlineConf(); err != nil { 47 | log.Error(err) 48 | } 49 | return nil 50 | }, 51 | } 52 | newJWTCmd = &cobra.Command{ 53 | Use: "new-jwt", 54 | Short: "Mailway instance generate a new JWT token", 55 | Args: cobra.NoArgs, 56 | RunE: func(cmd *cobra.Command, args []string) error { 57 | if err := newJWT(); err != nil { 58 | log.Fatal(err) 59 | } 60 | return nil 61 | }, 62 | } 63 | restartCmd = &cobra.Command{ 64 | Use: "restart", 65 | Short: "Restart Mailway services", 66 | Args: cobra.NoArgs, 67 | RunE: func(cmd *cobra.Command, args []string) error { 68 | services("restart") 69 | return nil 70 | }, 71 | } 72 | logsCmd = &cobra.Command{ 73 | Use: "logs", 74 | Short: "Display Mailway logs", 75 | Args: cobra.NoArgs, 76 | RunE: func(cmd *cobra.Command, args []string) error { 77 | logs() 78 | return nil 79 | }, 80 | } 81 | statusCmd = &cobra.Command{ 82 | Use: "status", 83 | Short: "Get Mailway services status", 84 | Args: cobra.NoArgs, 85 | RunE: func(cmd *cobra.Command, args []string) error { 86 | services("restart") 87 | return nil 88 | }, 89 | } 90 | updateCmd = &cobra.Command{ 91 | Use: "update", 92 | Short: "Update Mailway services", 93 | Args: cobra.NoArgs, 94 | RunE: func(cmd *cobra.Command, args []string) error { 95 | if err := update(); err != nil { 96 | return errors.Wrap(err, "could not update") 97 | } 98 | return nil 99 | }, 100 | } 101 | configCmd = &cobra.Command{ 102 | Use: "config", 103 | Short: "Print Mailway configuration", 104 | Args: cobra.NoArgs, 105 | RunE: func(cmd *cobra.Command, args []string) error { 106 | printConfig() 107 | return nil 108 | }, 109 | } 110 | supervisorCmd = &cobra.Command{ 111 | Use: "supervisor", 112 | Short: "Run Mailway supervisor", 113 | Args: cobra.NoArgs, 114 | RunE: func(cmd *cobra.Command, args []string) error { 115 | if err := supervise(); err != nil { 116 | return errors.Wrap(err, "failed to supervise") 117 | } 118 | return nil 119 | }, 120 | } 121 | recoverCmd = &cobra.Command{ 122 | Use: "recover [file]", 123 | Short: "Run Mailway supervisor", 124 | Args: cobra.MinimumNArgs(1), 125 | RunE: func(cmd *cobra.Command, args []string) error { 126 | if err := recoverEmail(args[0]); err != nil { 127 | return errors.Wrap(err, "could not recover email") 128 | } 129 | return nil 130 | }, 131 | } 132 | ) 133 | 134 | func init() { 135 | setupCmd.Flags().BoolVar(&isLocalSetup, "local", false, 136 | "Don't connect with Mailway API, run in local mode") 137 | 138 | rootCmd.AddCommand(setupCmd) 139 | rootCmd.AddCommand(setupSecureSMTPCmd) 140 | rootCmd.AddCommand(generateFrontlineConfigCmd) 141 | rootCmd.AddCommand(newJWTCmd) 142 | rootCmd.AddCommand(restartCmd) 143 | rootCmd.AddCommand(logsCmd) 144 | rootCmd.AddCommand(statusCmd) 145 | rootCmd.AddCommand(updateCmd) 146 | rootCmd.AddCommand(configCmd) 147 | rootCmd.AddCommand(supervisorCmd) 148 | rootCmd.AddCommand(recoverCmd) 149 | } 150 | -------------------------------------------------------------------------------- /cmd/mailway/dkim.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "io/ioutil" 9 | 10 | "github.com/pkg/errors" 11 | 12 | "github.com/mailway-app/config" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | func getDNSKey(pubKeyPath string) ([]byte, error) { 17 | bytes, err := ioutil.ReadFile(pubKeyPath) 18 | if err != nil { 19 | return []byte{}, errors.New("could not read public key file") 20 | } 21 | 22 | pubPem, _ := pem.Decode(bytes) 23 | if pubPem == nil { 24 | return []byte{}, errors.New("public key is not in PEM format") 25 | } 26 | 27 | pubKey, err := x509.ParsePKCS1PublicKey(pubPem.Bytes) 28 | if err != nil { 29 | return []byte{}, errors.Wrap(err, "could not read public RSA key") 30 | } 31 | 32 | // encode key for DNS 33 | bytes, err = x509.MarshalPKIXPublicKey(pubKey) 34 | if err != nil { 35 | return []byte{}, errors.Wrap(err, "could not marshal public key") 36 | } 37 | return bytes, nil 38 | } 39 | 40 | func generateDKIM() ([]byte, error) { 41 | certPath := "/etc/ssl/certs/mailway-dkim.pem" 42 | privPath := config.CurrConfig.OutDKIMPath 43 | 44 | if fileExists(certPath) || fileExists(privPath) { 45 | log.Warnf("%s or %s already exist; skipping DKIM key generation.", certPath, privPath) 46 | return getDNSKey(certPath) 47 | } 48 | 49 | reader := rand.Reader 50 | bitSize := 2048 51 | 52 | key, err := rsa.GenerateKey(reader, bitSize) 53 | if err != nil { 54 | return []byte{}, errors.Wrap(err, "could not generate RSA key") 55 | } 56 | 57 | // certificate 58 | if err := saveRSACert(certPath, &key.PublicKey); err != nil { 59 | return []byte{}, errors.Wrap(err, "could not save certificate") 60 | } 61 | 62 | // private key 63 | if err := savePrivateKey(privPath, key); err != nil { 64 | return []byte{}, errors.Wrap(err, "could not save private key") 65 | } 66 | 67 | err = config.WriteDKIM(privPath) 68 | if err != nil { 69 | return []byte{}, errors.Wrap(err, "could not write DKIM config") 70 | } 71 | 72 | return getDNSKey(certPath) 73 | } 74 | -------------------------------------------------------------------------------- /cmd/mailway/frontline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "text/template" 9 | 10 | "github.com/mailway-app/config" 11 | 12 | "github.com/pkg/errors" 13 | log "github.com/sirupsen/logrus" 14 | "golang.org/x/crypto/acme/autocert" 15 | ) 16 | 17 | func generateFrontlineConf() error { 18 | file := "/etc/mailway/frontline/nginx.conf" 19 | if fileExists(file) { 20 | log.Warnf("%s already exists; skipping frontline config generation.", file) 21 | return nil 22 | } 23 | 24 | tmpl := template.Must( 25 | template.ParseFiles("/etc/mailway/frontline/nginx.conf.tmpl")) 26 | 27 | dest, err := os.Create(file) 28 | if err != nil { 29 | return errors.Wrap(err, "could not create conf file") 30 | } 31 | defer dest.Close() 32 | 33 | err = tmpl.Execute(dest, config.CurrConfig) 34 | if err != nil { 35 | return errors.Wrap(err, "failed to render template") 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func loggingMiddleware(next http.Handler) http.Handler { 42 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 | log.Debugf("HTTP %s %s\n", r.Method, r.RequestURI) 44 | next.ServeHTTP(w, r) 45 | }) 46 | } 47 | 48 | func generateHTTPCert() error { 49 | certPath := fmt.Sprintf("/etc/ssl/certs/http-%s.pem", config.CurrConfig.InstanceHostname) 50 | privPath := fmt.Sprintf("/etc/ssl/private/http-%s.pem", config.CurrConfig.InstanceHostname) 51 | if fileExists(certPath) || fileExists(privPath) { 52 | log.Warnf("%s or %s already exist; skipping HTTPS key generation.", certPath, privPath) 53 | return nil 54 | } 55 | 56 | m := &autocert.Manager{ 57 | Prompt: autocert.AcceptTOS, 58 | Email: config.CurrConfig.InstanceEmail, 59 | } 60 | 61 | h := loggingMiddleware(m.HTTPHandler(nil)) 62 | go func() { 63 | log.Fatal(http.ListenAndServe(":http", h)) 64 | }() 65 | 66 | log.Infof("asking a certificate for %s; this could take a minute or two", config.CurrConfig.InstanceHostname) 67 | cert, err := m.GetCertificate(&tls.ClientHelloInfo{ 68 | ServerName: config.CurrConfig.InstanceHostname, 69 | }) 70 | if err != nil { 71 | return errors.Wrap(err, "could not generate certificate") 72 | } else { 73 | log.Info("OK") 74 | } 75 | 76 | // certificate 77 | if err := saveCert(certPath, cert.Certificate); err != nil { 78 | return errors.Wrap(err, "could not save certificate") 79 | } 80 | 81 | // private key 82 | if err := savePrivateKey(privPath, cert.PrivateKey); err != nil { 83 | return errors.Wrap(err, "could not save private key") 84 | } 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /cmd/mailway/jwt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "path" 9 | "time" 10 | 11 | mconfig "github.com/mailway-app/config" 12 | 13 | jwt "github.com/dgrijalva/jwt-go" 14 | "github.com/pkg/errors" 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | var ( 19 | httpClient = http.Client{ 20 | Timeout: time.Second * 5, 21 | } 22 | ) 23 | 24 | // instance's config sent by the Mailay API 25 | type instanceConfig struct { 26 | Hostname string `json:"hostname"` 27 | Email string `json:"email"` 28 | } 29 | 30 | type JWTClaims struct { 31 | jwt.StandardClaims 32 | Data instanceConfig `json:"data"` 33 | } 34 | 35 | type authorizeRes struct { 36 | Ok bool `json:"ok"` 37 | Data struct { 38 | JWT string `json:"jwt"` 39 | } `json:"data"` 40 | } 41 | 42 | func lookupPublicKey() (interface{}, error) { 43 | data, err := ioutil.ReadFile(path.Join(mconfig.ROOT_LOCATION, "key.pub")) 44 | if err != nil { 45 | return nil, errors.Wrap(err, "could not read key file") 46 | } 47 | return jwt.ParseRSAPublicKeyFromPEM(data) 48 | } 49 | 50 | func authorize(serverId string) (string, error) { 51 | url := fmt.Sprintf("%s/instance/%s/authorize", API_BASE_URL, serverId) 52 | log.Debugf("request to %s", url) 53 | 54 | req, err := http.NewRequest(http.MethodGet, url, nil) 55 | if err != nil { 56 | return "", err 57 | } 58 | 59 | req.Header.Set("User-Agent", "mailway-self-host") 60 | 61 | res, getErr := httpClient.Do(req) 62 | if getErr != nil { 63 | return "", errors.Wrap(getErr, "could not send request") 64 | } 65 | 66 | if res.Body != nil { 67 | defer res.Body.Close() 68 | } 69 | 70 | body, readErr := ioutil.ReadAll(res.Body) 71 | if readErr != nil { 72 | return "", errors.Wrap(readErr, "couldn't read body") 73 | } 74 | 75 | d := authorizeRes{} 76 | jsonErr := json.Unmarshal(body, &d) 77 | if jsonErr != nil { 78 | return "", errors.Wrap(jsonErr, "could not parse JSON") 79 | } 80 | 81 | err = mconfig.WriteServerJWT(d.Data.JWT) 82 | if err != nil { 83 | return "", errors.Wrap(jsonErr, "could not write JWT") 84 | } 85 | 86 | return d.Data.JWT, nil 87 | } 88 | 89 | func parseJWT(v string) (*jwt.Token, error) { 90 | claims := new(JWTClaims) 91 | token, err := jwt.ParseWithClaims(v, claims, func(token *jwt.Token) (interface{}, error) { 92 | return lookupPublicKey() 93 | }) 94 | 95 | if err != nil || !token.Valid { 96 | return nil, errors.Wrap(err, "key failed to verify") 97 | } 98 | if err := token.Claims.Valid(); err != nil { 99 | return nil, errors.Wrap(err, "JWT claims not valid") 100 | } 101 | 102 | return token, nil 103 | } 104 | 105 | func getJWTData(token *jwt.Token) (instanceConfig, error) { 106 | claims := token.Claims.(*JWTClaims) 107 | return claims.Data, nil 108 | } 109 | -------------------------------------------------------------------------------- /cmd/mailway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | 11 | "github.com/mailway-app/config" 12 | 13 | "github.com/pkg/errors" 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | const ( 18 | API_BASE_URL = "https://apiv1.mailway.app" 19 | ) 20 | 21 | var ( 22 | SERVICES = []string{ 23 | "mailout", 24 | "maildb", 25 | "auth", 26 | "forwarding", 27 | "frontline", 28 | "mailway-supervisor", 29 | "webhooks", 30 | } 31 | ) 32 | 33 | func fileExists(filename string) bool { 34 | info, err := os.Stat(filename) 35 | if os.IsNotExist(err) { 36 | return false 37 | } 38 | if info == nil { 39 | return false 40 | } 41 | return !info.IsDir() 42 | } 43 | 44 | func printConfig() { 45 | s, err := config.PrettyPrint() 46 | if err != nil { 47 | panic(err) 48 | } 49 | fmt.Printf("%s\n", s) 50 | } 51 | 52 | // Get preferred outbound ip of this machine 53 | func GetOutboundIP() (*net.IP, error) { 54 | url := "https://api.ipify.org?format=text" 55 | resp, err := http.Get(url) 56 | if err != nil { 57 | return nil, errors.Wrap(err, "failed to call the ip api") 58 | } 59 | defer resp.Body.Close() 60 | body, err := ioutil.ReadAll(resp.Body) 61 | if err != nil { 62 | return nil, errors.Wrap(err, "could not read response body") 63 | } 64 | ip := net.ParseIP(string(body[:])) 65 | return &ip, nil 66 | } 67 | 68 | func services(action string) { 69 | for _, service := range SERVICES { 70 | cmd := exec.Command("systemctl", action, service) 71 | cmd.Stdout = os.Stdout 72 | cmd.Stderr = os.Stderr 73 | log.Debugf("running: %s", cmd) 74 | err := cmd.Run() 75 | if err != nil { 76 | log.Errorf("failed to %s service %s: %s", action, service, err) 77 | } 78 | } 79 | } 80 | 81 | func logs() { 82 | args := make([]string, 0) 83 | args = append(args, "-f") 84 | for _, service := range SERVICES { 85 | args = append(args, "-u") 86 | args = append(args, service) 87 | } 88 | cmd := exec.Command("journalctl", args...) 89 | log.Debugf("running command: %s", cmd) 90 | cmd.Stdout = os.Stdout 91 | cmd.Stderr = os.Stderr 92 | err := cmd.Run() 93 | if err != nil { 94 | log.Errorf("failed to read logs: %s", err) 95 | } 96 | } 97 | 98 | func setupSecureSmtp() error { 99 | log.Info("Install certbot") 100 | cmd := exec.Command("apt-get", "install", "-y", "certbot") 101 | log.Debug(cmd) 102 | cmd.Stdout = os.Stdout 103 | cmd.Stderr = os.Stderr 104 | err := cmd.Run() 105 | if err != nil { 106 | return errors.Wrap(err, "failed to install certbot") 107 | } 108 | 109 | log.Info("Run certbot") 110 | cmd = exec.Command("certbot", "certonly", "--manual", 111 | "--domain="+config.CurrConfig.InstanceHostname, "--email="+config.CurrConfig.InstanceEmail, 112 | "--cert-name=smtp-"+config.CurrConfig.InstanceHostname, "--preferred-challenges=dns") 113 | log.Debug(cmd) 114 | cmd.Stdout = os.Stdout 115 | cmd.Stderr = os.Stderr 116 | cmd.Stdin = os.Stdin 117 | err = cmd.Run() 118 | if err != nil { 119 | return errors.Wrap(err, "failed to install certbot") 120 | } 121 | 122 | return nil 123 | } 124 | 125 | func newJWT() error { 126 | jwt, err := authorize(config.CurrConfig.ServerId) 127 | if err != nil { 128 | return errors.Wrap(err, "failed to call authorize") 129 | } 130 | token, err := parseJWT(jwt) 131 | if err != nil { 132 | return errors.Wrap(err, "failed to parse JWT") 133 | } 134 | data, err := getJWTData(token) 135 | if err != nil { 136 | return errors.Wrap(err, "failed to get JWT data") 137 | } 138 | err = config.WriteInstanceConfig("connected", data.Hostname, data.Email) 139 | if err != nil { 140 | return errors.Wrap(err, "failed to write config") 141 | } 142 | return nil 143 | } 144 | 145 | func main() { 146 | if err := config.Init(); err != nil { 147 | log.Fatalf("failed to init config: %s", err) 148 | } 149 | 150 | if err := rootCmd.Execute(); err != nil { 151 | log.Fatalf("failed to run command: %s", err) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /cmd/mailway/pem.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "os" 9 | 10 | "github.com/pkg/errors" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func saveRSACert(name string, pubkey *rsa.PublicKey) error { 15 | log.Debugf("write certificate %s", name) 16 | f, err := os.Create(name) 17 | if err != nil { 18 | return errors.Wrap(err, "could not create file") 19 | } 20 | bytes := x509.MarshalPKCS1PublicKey(pubkey) 21 | err = pem.Encode(f, &pem.Block{ 22 | Type: "PUBLIC KEY", 23 | Bytes: bytes, 24 | }) 25 | if err != nil { 26 | return errors.Wrap(err, "could not encode cert") 27 | } 28 | return nil 29 | } 30 | 31 | func saveCert(name string, cert [][]byte) error { 32 | log.Debugf("write certificate %s", name) 33 | f, err := os.Create(name) 34 | if err != nil { 35 | return errors.Wrap(err, "could not create file") 36 | } 37 | defer f.Close() 38 | 39 | for _, bytes := range cert { 40 | err = pem.Encode(f, &pem.Block{ 41 | Type: "CERTIFICATE", 42 | Bytes: bytes, 43 | }) 44 | if err != nil { 45 | return errors.Wrap(err, "could not encode cert") 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | func savePrivateKey(name string, key crypto.PrivateKey) error { 52 | log.Debugf("write private key %s", name) 53 | f, err := os.Create(name) 54 | if err != nil { 55 | return errors.Wrap(err, "could not create file") 56 | } 57 | err = pem.Encode(f, &pem.Block{ 58 | Type: "RSA PRIVATE KEY", 59 | Bytes: x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey)), 60 | }) 61 | if err != nil { 62 | return errors.Wrap(err, "could not encode private key") 63 | } 64 | 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /cmd/mailway/preflight.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | "github.com/mailway-app/config" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func runPreflightChecks() error { 14 | port := config.CurrConfig.PortFrontlineSMTP 15 | if ok, _ := testPort(port); !ok { 16 | return errors.Errorf("port %d appears to be blocked", port) 17 | } 18 | return nil 19 | } 20 | 21 | func testPort(port int) (bool, error) { 22 | addr := fmt.Sprintf("portquiz.net:%d", port) 23 | timeout := 3 * time.Second 24 | c, err := net.DialTimeout("tcp", addr, timeout) 25 | if err != nil { 26 | return false, err 27 | } 28 | c.Close() 29 | return true, nil 30 | } 31 | -------------------------------------------------------------------------------- /cmd/mailway/recover.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/mail" 8 | "net/smtp" 9 | "os" 10 | 11 | "github.com/mailway-app/config" 12 | 13 | "github.com/pkg/errors" 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | func recoverEmail(file string) error { 18 | data, err := ioutil.ReadFile(file) 19 | if err != nil { 20 | return errors.Wrap(err, "could not read file") 21 | } 22 | 23 | msg, err := mail.ReadMessage(bytes.NewReader(data)) 24 | if err != nil { 25 | return errors.Wrap(err, "could not read message") 26 | } 27 | 28 | from := msg.Header.Get("Mw-Int-Mail-From") 29 | to := msg.Header.Get("Mw-Int-Rcpt-To") 30 | via := msg.Header.Get("Mw-Int-Via") 31 | 32 | if via == "forwarding" { 33 | addr := fmt.Sprintf("127.0.0.1:%d", config.CurrConfig.PortForwarding) 34 | err = smtp.SendMail(addr, nil, from, []string{to}, data) 35 | if err != nil { 36 | return errors.Wrap(err, "could not send email") 37 | } 38 | 39 | // no failures detected so far means that the message has made it back into 40 | // the system, we can go ahead and delete the file. If another error occur 41 | // a new file will be created 42 | if err := os.Remove(file); err != nil { 43 | return errors.Wrap(err, "could not delete file") 44 | } 45 | log.Info("mail sent") 46 | return nil 47 | } 48 | 49 | if via == "responder" { 50 | addr := fmt.Sprintf("127.0.0.1:%d", config.CurrConfig.PortResponder) 51 | err = smtp.SendMail(addr, nil, from, []string{to}, data) 52 | if err != nil { 53 | return errors.Wrap(err, "could not send email") 54 | } 55 | 56 | // no failures detected so far means that the message has made it back into 57 | // the system, we can go ahead and delete the file. If another error occur 58 | // a new file will be created 59 | if err := os.Remove(file); err != nil { 60 | return errors.Wrap(err, "could not delete file") 61 | } 62 | log.Info("mail sent") 63 | return nil 64 | } 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /cmd/mailway/setup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net" 7 | "net/url" 8 | "os" 9 | "time" 10 | 11 | "github.com/mailway-app/config" 12 | 13 | valid "github.com/asaskevich/govalidator" 14 | "github.com/manifoldco/promptui" 15 | "github.com/pkg/errors" 16 | log "github.com/sirupsen/logrus" 17 | ) 18 | 19 | func prompConfirm(isOptional bool, msg string) { 20 | label := fmt.Sprintf("Did you %s", msg) 21 | if v := os.Getenv("DEBIAN_FRONTEND"); v == "noninteractive" { 22 | log.Infof("%s? Assuming yes because output is not a tty", label) 23 | return 24 | } 25 | prompt := promptui.Prompt{ 26 | Label: label, 27 | IsConfirm: true, 28 | Default: "y", 29 | } 30 | 31 | _, err := prompt.Run() 32 | if err != nil { 33 | if !isOptional { 34 | log.Fatalf("Before proceeding with the setup you need to: %s", msg) 35 | } 36 | } 37 | } 38 | 39 | func getText(msg string, validation string) string { 40 | validate := func(input string) error { 41 | switch validation { 42 | case "domain": 43 | if !valid.IsDNSName(input) { 44 | return errors.New("Domain name must be valid") 45 | } 46 | case "email": 47 | if !valid.IsEmail(input) { 48 | return errors.New("Email address must be valid") 49 | } 50 | default: 51 | panic("unknown validation") 52 | } 53 | return nil 54 | } 55 | 56 | prompt := promptui.Prompt{ 57 | Label: msg, 58 | Validate: validate, 59 | } 60 | result, err := prompt.Run() 61 | if err != nil { 62 | log.Fatalf("Prompt failed %v\n", err) 63 | return "" 64 | } 65 | return result 66 | } 67 | 68 | func setupConnected(ip *net.IP, dkim string) error { 69 | url := fmt.Sprintf( 70 | "https://dash.mailway.app/helo?server_id=%s&ip=%s&dkim=%s", 71 | config.CurrConfig.ServerId, ip, url.QueryEscape(dkim)) 72 | fmt.Printf("Open %s\n", url) 73 | 74 | ticker := time.NewTicker(2 * time.Second) 75 | quit := make(chan struct{}) 76 | for { 77 | select { 78 | case <-ticker.C: 79 | jwt, err := authorize(config.CurrConfig.ServerId) 80 | if err != nil { 81 | panic(err) 82 | } 83 | if jwt == "" { 84 | continue 85 | } 86 | ticker.Stop() 87 | log.Info("instance connected with Mailway") 88 | token, err := parseJWT(jwt) 89 | if err != nil { 90 | panic(err) 91 | } 92 | data, err := getJWTData(token) 93 | if err != nil { 94 | panic(err) 95 | } 96 | err = config.WriteInstanceConfig("connected", data.Hostname, data.Email) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | if err := generateFrontlineConf(); err != nil { 102 | return errors.Wrap(err, "could not generate frontline conf") 103 | } 104 | if err := generateHTTPCert(); err != nil { 105 | return errors.Wrap(err, "could not generate certificates for HTTP") 106 | } 107 | 108 | log.Info("Setup completed; starting email service") 109 | services("start") 110 | close(quit) 111 | case <-quit: 112 | ticker.Stop() 113 | return nil 114 | } 115 | } 116 | } 117 | 118 | func setupLocal(ip *net.IP, dkim string) error { 119 | var hostname string 120 | var email string 121 | 122 | if v := os.Getenv("MW_HOSTNAME"); v != "" { 123 | hostname = v 124 | } else { 125 | hostname = getText("Please enter the name of your email server (for example: mx.example.com)", "domain") 126 | } 127 | 128 | dnsFields := func(name, value string) string { 129 | return fmt.Sprintf("Name: %s\nValue:\n\n%s\n", name, value) 130 | } 131 | 132 | fmt.Printf("Add a DNS record (type A);\n%s\n", dnsFields(hostname, ip.String())) 133 | prompConfirm(false, "add the A DNS record") 134 | 135 | fmt.Printf("Optionally, add a DNS record (type TXT):\n%s\n", 136 | dnsFields(hostname, fmt.Sprintf("v=spf1 ip4:%s/32 ~all", ip))) 137 | prompConfirm(true, "add the TXT DNS record") 138 | 139 | fmt.Printf("Optionally, add a DNS record (type TXT):\n%s\n", 140 | dnsFields("smtp._domainkey."+hostname, fmt.Sprintf("v=DKIM1; k=rsa; p=%s", dkim))) 141 | prompConfirm(true, "add the TXT DNS record") 142 | 143 | if v := os.Getenv("MW_EMAIL"); v != "" { 144 | email = v 145 | } else { 146 | email = getText("Please enter your email address (email will only be used to generate certificates)", "email") 147 | } 148 | 149 | err := config.WriteInstanceConfig("local", hostname, email) 150 | if err != nil { 151 | return errors.Wrap(err, "could not write instance config") 152 | } 153 | 154 | if err := generateFrontlineConf(); err != nil { 155 | return errors.Wrap(err, "could not generate frontline conf") 156 | } 157 | 158 | log.Info("Setup completed; starting email service") 159 | services("start") 160 | return nil 161 | } 162 | 163 | func setup() error { 164 | if isLocalSetup { 165 | log.Info("Setup for local mode") 166 | } 167 | 168 | if err := runPreflightChecks(); err != nil { 169 | if v := os.Getenv("DEBIAN_FRONTEND"); v == "noninteractive" { 170 | log.Infof("The preflight checks failed because: %s. Ignoring and continuing because output is not a tty", err) 171 | } else { 172 | prompt := promptui.Prompt{ 173 | Label: fmt.Sprintf("The preflight checks failed because: %s. Confirm to ignore and continue the setup", err), 174 | IsConfirm: true, 175 | } 176 | 177 | if _, err := prompt.Run(); err != nil { 178 | return errors.New("The preflight checks failed") 179 | } 180 | } 181 | } else { 182 | log.Info("preflight check passed") 183 | } 184 | 185 | dkim, err := generateDKIM() 186 | if err != nil { 187 | return errors.Wrap(err, "could not generate DKIM keys") 188 | } 189 | 190 | ip, err := GetOutboundIP() 191 | if err != nil { 192 | return errors.Wrap(err, "could not get outbound IP") 193 | } 194 | 195 | base64Dkim := base64.StdEncoding.EncodeToString(dkim) 196 | 197 | if isLocalSetup { 198 | return setupLocal(ip, base64Dkim) 199 | } else { 200 | return setupConnected(ip, base64Dkim) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /cmd/mailway/supervisor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "strings" 8 | "time" 9 | 10 | "github.com/mailway-app/config" 11 | 12 | "github.com/pkg/errors" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | var ( 17 | JWT_CHECK_INTERVAL = 1 * time.Hour 18 | 19 | MAILOUT_RETRY_INTERVAL = 3 * time.Minute 20 | MAILWAY_RETRY_SEQ = []int{-1, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 21 | 233, 377, 610, 987, 1597, 2584, 4181} 22 | ) 23 | 24 | func supervise() error { 25 | done := make(chan interface{}) 26 | 27 | if !config.CurrConfig.IsInstanceLocal() { 28 | go func() { 29 | if err := superviseServerJWT(); err != nil { 30 | log.Fatalf("failed to supervise server JWT: %s", err) 31 | } 32 | }() 33 | } 34 | go func() { 35 | if err := superviseMailoutRetrier(); err != nil { 36 | log.Fatalf("failed to supervise mailout retrier: %s", err) 37 | } 38 | }() 39 | 40 | <-done 41 | return nil 42 | } 43 | 44 | func getNextRetry(modTime time.Time, retry int) (time.Time, error) { 45 | if retry > len(MAILWAY_RETRY_SEQ) { 46 | return time.Time{}, errors.Errorf("too many retries (%d), ignoring", retry) 47 | } 48 | n := time.Duration(MAILWAY_RETRY_SEQ[retry]) * 3 * time.Minute 49 | return modTime.Add(n), nil 50 | } 51 | 52 | func retryCount(data []byte) int { 53 | return strings.Count(string(data), "Mw-Int-Id") 54 | } 55 | 56 | func superviseMailoutRetrier() error { 57 | log.Info("mailout retrier running") 58 | for range time.Tick(MAILOUT_RETRY_INTERVAL) { 59 | files, err := ioutil.ReadDir(config.RUNTIME_LOCATION) 60 | if err != nil { 61 | return errors.Wrapf(err, "failed to read %s", config.RUNTIME_LOCATION) 62 | } 63 | 64 | for _, file := range files { 65 | abspath := path.Join(config.RUNTIME_LOCATION, file.Name()) 66 | data, err := ioutil.ReadFile(abspath) 67 | if err != nil { 68 | return errors.Wrap(err, "could not read file") 69 | } 70 | 71 | // ensures that the file has been in the queue for long enough 72 | if time.Since(file.ModTime()) < MAILOUT_RETRY_INTERVAL { 73 | continue 74 | } 75 | 76 | retryCount := retryCount(data) 77 | nextRetry, err := getNextRetry(file.ModTime(), retryCount) 78 | if err != nil { 79 | log.Errorf("could not retry: %s", err) 80 | continue 81 | } 82 | 83 | if nextRetry.Before(time.Now()) { 84 | log.Infof("%s retried %d time(s), retyring now", abspath, retryCount) 85 | if err := recoverEmail(abspath); err != nil { 86 | log.Errorf("failed to recover email: %s", err) 87 | 88 | // delete the email since a new buffer will be created from 89 | // the failure 90 | if err := os.Remove(abspath); err != nil { 91 | return errors.Wrap(err, "could not delete file") 92 | } 93 | } 94 | } else { 95 | log.Infof("%s retried %d time(s) next retry in %v", 96 | abspath, retryCount, time.Until(nextRetry)) 97 | } 98 | } 99 | } 100 | return nil 101 | } 102 | 103 | func superviseServerJWT() error { 104 | log.Info("server JWT watcher running") 105 | for range time.Tick(JWT_CHECK_INTERVAL) { 106 | token := config.CurrConfig.ServerJWT 107 | if token == "" { 108 | log.Warn("no existing token; did you forgot to run mailway setup?") 109 | continue 110 | } 111 | 112 | jwt, err := parseJWT(token) 113 | if err != nil { 114 | log.Warnf("failed to parse exiting token: %s; asking new one", err) 115 | if err := newJWT(); err != nil { 116 | log.Errorf("failed to get new JWT: %s", err) 117 | } 118 | continue 119 | } 120 | 121 | claims := jwt.Claims.(*JWTClaims) 122 | 123 | now := time.Now().Unix() 124 | delta := time.Unix(claims.ExpiresAt, 0).Sub(time.Unix(now, 0)) 125 | 126 | log.Debugf("server JWT has %v to live", delta) 127 | 128 | if delta < 24*time.Hour { 129 | log.Infof("Token almost expired; asking new one") 130 | if err := newJWT(); err != nil { 131 | log.Errorf("failed to get new JWT: %s", err) 132 | } 133 | } 134 | 135 | } 136 | 137 | return nil 138 | } 139 | -------------------------------------------------------------------------------- /cmd/mailway/update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | log "github.com/sirupsen/logrus" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | func aptInstall(pkg string) error { 11 | cmd := exec.Command("apt-get", "install", "-y", pkg) 12 | log.Debugf("running command: %s", cmd) 13 | cmd.Stdout = os.Stdout 14 | cmd.Stderr = os.Stderr 15 | err := cmd.Run() 16 | if err != nil { 17 | return errors.Wrapf(err, "failed to update %s", pkg) 18 | } 19 | return nil 20 | } 21 | 22 | func aptUpdate() error { 23 | cmd := exec.Command("apt-get", "update") 24 | log.Debugf("running command: %s", cmd) 25 | cmd.Stdout = os.Stdout 26 | cmd.Stderr = os.Stderr 27 | err := cmd.Run() 28 | if err != nil { 29 | return errors.Wrapf(err, "failed to update metadata") 30 | } 31 | return nil 32 | } 33 | 34 | func update() error { 35 | if err := aptUpdate(); err != nil { 36 | log.Error(err) 37 | } 38 | if err := aptInstall("mailway"); err != nil { 39 | log.Error(err) 40 | } 41 | for _, service := range SERVICES { 42 | if service == "mailway-supervisor" { 43 | continue 44 | } 45 | if err := aptInstall(service); err != nil { 46 | log.Error(err) 47 | } 48 | } 49 | 50 | services("restart") 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /conf.d/dkim.yml: -------------------------------------------------------------------------------- 1 | out_dkim_path: /etc/ssl/private/mailway-dkim.pem 2 | -------------------------------------------------------------------------------- /conf.d/logs.yml: -------------------------------------------------------------------------------- 1 | log_frontline_error: /var/log/frontline-nginx/debug.log 2 | log_frontline_http_access: /var/log/frontline-nginx-http/access.log 3 | log_frontline_http_error: /var/log/frontline-nginx-http/error.log 4 | -------------------------------------------------------------------------------- /conf.d/mailway.yml: -------------------------------------------------------------------------------- 1 | log_level: INFO 2 | -------------------------------------------------------------------------------- /conf.d/ports.yml: -------------------------------------------------------------------------------- 1 | port_frontline_smtp: 25 2 | port_frontline_smtps: 587 3 | port_auth: 9000 4 | port_mailout: 2525 5 | port_webhook: 2526 6 | port_forwarding: 2500 7 | port_maildb: 8081 8 | port_responder: 2501 9 | -------------------------------------------------------------------------------- /conf.d/spam.yml: -------------------------------------------------------------------------------- 1 | spam_filter: false 2 | -------------------------------------------------------------------------------- /frontline/nginx.conf.tmpl: -------------------------------------------------------------------------------- 1 | daemon off; 2 | error_log {{ .LogFrontlineError }} warn; 3 | worker_processes auto; 4 | 5 | events { 6 | worker_connections 1024; 7 | } 8 | 9 | mail { 10 | server { 11 | listen 0.0.0.0:{{ .PortFrontlineSMTP }}; 12 | protocol smtp; 13 | smtp_auth none; 14 | proxy on; 15 | # starttls on; 16 | 17 | auth_http 127.0.0.1:{{ .PortAuth }}; 18 | 19 | xclient on; 20 | proxy_pass_error_message off; 21 | 22 | # ssl_certificate /etc/letsencrypt/live/smtp-{{ .InstanceHostname }}/fullchain.pem; 23 | # ssl_certificate_key /etc/letsencrypt/live/smtp-{{ .InstanceHostname }}/privkey.pem; 24 | # ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 25 | } 26 | 27 | # server { 28 | # listen 0.0.0.0:{{ .PortFrontlineSMTPS }} ssl; 29 | # protocol smtp; 30 | # smtp_auth none; 31 | # proxy on; 32 | 33 | # server_name {{ .InstanceHostname }}; 34 | 35 | # auth_http 127.0.0.1:{{ .PortAuth }}; 36 | 37 | # xclient on; 38 | # proxy_pass_error_message off; 39 | 40 | # ssl_certificate /etc/letsencrypt/live/smtp-{{ .InstanceHostname }}/fullchain.pem; 41 | # ssl_certificate_key /etc/letsencrypt/live/smtp-{{ .InstanceHostname }}/privkey.pem; 42 | # ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 43 | # } 44 | } 45 | 46 | {{if ne .InstanceMode "local" }} 47 | http { 48 | charset utf-8; 49 | access_log {{ .LogFrontlineHTTPAccess }}; 50 | error_log {{ .LogFrontlineHTTPError }}; 51 | 52 | server { 53 | listen 0.0.0.0:443 ssl; 54 | listen [::]:443 ssl; 55 | 56 | server_name {{ .InstanceHostname }}; 57 | ssl_certificate /etc/ssl/certs/http-{{ .InstanceHostname }}.pem; 58 | ssl_certificate_key /etc/ssl/private/http-{{ .InstanceHostname }}.pem; 59 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 60 | 61 | ssl_session_timeout 1d; 62 | ssl_session_cache shared:SSL:50m; 63 | ssl_session_tickets off; 64 | 65 | location /db/ { 66 | proxy_pass http://127.0.0.1:{{ .PortMaildb }}/db/; 67 | } 68 | } 69 | } 70 | {{end}} 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mailway-app/mailway 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/magefile/mage v1.11.0 // indirect 9 | github.com/mailway-app/config v0.0.0-20210513211133-2f31c01469d5 10 | github.com/manifoldco/promptui v0.8.0 11 | github.com/pkg/errors v0.9.1 12 | github.com/sirupsen/logrus v1.8.1 13 | github.com/spf13/cobra v1.1.3 14 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad 15 | golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 17 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 21 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 22 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= 23 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 24 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 25 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 26 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 27 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 28 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 29 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 30 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 31 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 32 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 33 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 34 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 35 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 36 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 37 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 38 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 39 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 40 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 41 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 42 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 43 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 44 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 45 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 46 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 47 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 48 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 49 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 50 | github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae/go.mod h1:BbhqyaehKPCLD83cqfRYdm177Ylm1cdGHu3txjbQSQI= 51 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 52 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 53 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 54 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 55 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 56 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 57 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 58 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 59 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 60 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 61 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 62 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 63 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 64 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 65 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 66 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 67 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 68 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 69 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 70 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 71 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 72 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 73 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 74 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 75 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 76 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 77 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 78 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 79 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 80 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 81 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 82 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 83 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 84 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 85 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 86 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 87 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 88 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 89 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 90 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 91 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 92 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 93 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 94 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 95 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 96 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 97 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 98 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 99 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 100 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 101 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 102 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 103 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 104 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 105 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 106 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 107 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= 108 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 109 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 110 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 111 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 112 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 113 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 114 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 115 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 116 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 117 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= 118 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 119 | github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= 120 | github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 121 | github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= 122 | github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 123 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 124 | github.com/mailway-app/config v0.0.0-20210121223557-78a03ec5f59a h1:vWhExg2TLyblno5UI8XX3Bkm0M3b7eI/26U3FcDTpM4= 125 | github.com/mailway-app/config v0.0.0-20210121223557-78a03ec5f59a/go.mod h1:7Uxyjt0aFj34+PEBfhaPGomhPav4UKbtCD6J9z17sz4= 126 | github.com/mailway-app/config v0.0.0-20210121223914-aa8076bc6c86 h1:UqSwr7F1bRoTxK1XSjRqM0BzeHnaHydqFLWOFN3FeHs= 127 | github.com/mailway-app/config v0.0.0-20210121223914-aa8076bc6c86/go.mod h1:7Uxyjt0aFj34+PEBfhaPGomhPav4UKbtCD6J9z17sz4= 128 | github.com/mailway-app/config v0.0.0-20210124181426-93fcdd03aabf h1:hPgaJfRNz3G26TIMKbX9TXO08gkE8IkOYELfrvH6MWo= 129 | github.com/mailway-app/config v0.0.0-20210124181426-93fcdd03aabf/go.mod h1:7Uxyjt0aFj34+PEBfhaPGomhPav4UKbtCD6J9z17sz4= 130 | github.com/mailway-app/config v0.0.0-20210124181747-1282af483b61 h1:VtJw/D2JtdimecKkx6uQ9ju1cGMUcLzm1D6+3dAJdfQ= 131 | github.com/mailway-app/config v0.0.0-20210124181747-1282af483b61/go.mod h1:7Uxyjt0aFj34+PEBfhaPGomhPav4UKbtCD6J9z17sz4= 132 | github.com/mailway-app/config v0.0.0-20210124184150-485238e4123e h1:Vda0VmMXsktMhrg3crcg8RQoJ9aLcM7RsWbThi5SmCs= 133 | github.com/mailway-app/config v0.0.0-20210124184150-485238e4123e/go.mod h1:7Uxyjt0aFj34+PEBfhaPGomhPav4UKbtCD6J9z17sz4= 134 | github.com/mailway-app/config v0.0.0-20210127085910-a38e80cb08c4 h1:lAIlspEhmRR2lw3wLocHvr7mI3Co15u5+sPRV1ymtcA= 135 | github.com/mailway-app/config v0.0.0-20210127085910-a38e80cb08c4/go.mod h1:7Uxyjt0aFj34+PEBfhaPGomhPav4UKbtCD6J9z17sz4= 136 | github.com/mailway-app/config v0.0.0-20210131160909-647d5a2342b7 h1:fI9AP/qpizgesKapWBhJUoKJwe0WgyTw5YaktToe/Uc= 137 | github.com/mailway-app/config v0.0.0-20210131160909-647d5a2342b7/go.mod h1:gjmRcvcb1UaRgtEMyfASF1XDXB2t0AFNKK+fJ8xM3NQ= 138 | github.com/mailway-app/config v0.0.0-20210131161027-17737b0f1d0c h1:Cg2CwynkGHBPPtyGcLIL5J3zM+rEejx4Wex17oid1Uk= 139 | github.com/mailway-app/config v0.0.0-20210131161027-17737b0f1d0c/go.mod h1:gjmRcvcb1UaRgtEMyfASF1XDXB2t0AFNKK+fJ8xM3NQ= 140 | github.com/mailway-app/config v0.0.0-20210214145136-8a381a1ce901 h1:cP6LytSdHAszaO674+xQQUqUeZkgzNcpbpWjrYX4K9U= 141 | github.com/mailway-app/config v0.0.0-20210214145136-8a381a1ce901/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 142 | github.com/mailway-app/config v0.0.0-20210214151609-9f149a8cacb4 h1:KwzJViAGYE7aDYsbSoJ8m/ou7VWfKO823olSbuIccFs= 143 | github.com/mailway-app/config v0.0.0-20210214151609-9f149a8cacb4/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 144 | github.com/mailway-app/config v0.0.0-20210214183823-a092c8c3a426 h1:fDFA6ib0DesgWZRPV7AGOOCyvyVngiX8q522VKuGFOg= 145 | github.com/mailway-app/config v0.0.0-20210214183823-a092c8c3a426/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 146 | github.com/mailway-app/config v0.0.0-20210214191811-c8e5cbf784b8 h1:3UDxub1zHCQ0zAgDHOavwpYY2SBXo2AZ1Vl/Oj988D4= 147 | github.com/mailway-app/config v0.0.0-20210214191811-c8e5cbf784b8/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 148 | github.com/mailway-app/config v0.0.0-20210217201023-ad8998973680 h1:znKS9AzSLB8H1yddYFzoi6CSa0BQ+fMZjRytkVln+9g= 149 | github.com/mailway-app/config v0.0.0-20210217201023-ad8998973680/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 150 | github.com/mailway-app/config v0.0.0-20210221175155-59f4d4fc9427 h1:rCK8C0SgA0gxGU1KIG0YTIU9ZY+KXCE4Lw63IE2wjbE= 151 | github.com/mailway-app/config v0.0.0-20210221175155-59f4d4fc9427/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 152 | github.com/mailway-app/config v0.0.0-20210227210532-ddd38d4b4000 h1:r6l6dAp2R0kC/TvxEGhWeggfL2D3sM2lmnKnSW998T4= 153 | github.com/mailway-app/config v0.0.0-20210227210532-ddd38d4b4000/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 154 | github.com/mailway-app/config v0.0.0-20210228114952-43e0fa42e09f h1:v5dG2HO/8lI7BS+I2avO3WGP/YK166Z6nf4NKUhRPos= 155 | github.com/mailway-app/config v0.0.0-20210228114952-43e0fa42e09f/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 156 | github.com/mailway-app/config v0.0.0-20210513211133-2f31c01469d5 h1:be1rFNpZmAztT0gJmmHIpLp1v7PAoMIum1LGUcIuSj8= 157 | github.com/mailway-app/config v0.0.0-20210513211133-2f31c01469d5/go.mod h1:wIptp9O+DqeKdh/SfCHLj1eg/nkDNugSGgn0JNY1ag8= 158 | github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= 159 | github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= 160 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 161 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 162 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 163 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 164 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 165 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 166 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 167 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 168 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 169 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 170 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 171 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 172 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 173 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 174 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 175 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 176 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 177 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 178 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 179 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 180 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 181 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 182 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 183 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 184 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 185 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 186 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 187 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 188 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 189 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 190 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 191 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 192 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 193 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 194 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 195 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 196 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 197 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 198 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 199 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 200 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 201 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 202 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 203 | github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= 204 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 205 | github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= 206 | github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= 207 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 208 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 209 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 210 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 211 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 212 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 213 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 214 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 215 | github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= 216 | github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= 217 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 218 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 219 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 220 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 221 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 222 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 223 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 224 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 225 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 226 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 227 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 228 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 229 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 230 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 231 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 232 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 233 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 234 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 235 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 236 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 237 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 238 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 239 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 240 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= 241 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 242 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 243 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 244 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 245 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 246 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 247 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 248 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 249 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 250 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 251 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 252 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 253 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 254 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 255 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 256 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 257 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 258 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 259 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 260 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 261 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 262 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 263 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 264 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 265 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 266 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 267 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 268 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 269 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= 270 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 271 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 272 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 273 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 274 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= 275 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 276 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 277 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 278 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 279 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 280 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 281 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 282 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 283 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 284 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 285 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 286 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 287 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 288 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 289 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 290 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 291 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 292 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 300 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= 302 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20210217105451-b926d437f341 h1:2/QtM1mL37YmcsT8HaDNHDgTqqFVw+zr8UzMiBVLzYU= 304 | golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE= 306 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d h1:9fH9JvLNoSpsDWcXJ4dSE3lZW99Z3OCUZLr07g60U6o= 308 | golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= 310 | golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 311 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 312 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 313 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 314 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 315 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 316 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 317 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 318 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 319 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 320 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 321 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 322 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 323 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 324 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 325 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 326 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 327 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 328 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 329 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 330 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 331 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 332 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 333 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 334 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 335 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 336 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 337 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 338 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 339 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 340 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 341 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 342 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 343 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 344 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 345 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 346 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 347 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 348 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 349 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 350 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 351 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 352 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 353 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 354 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 355 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 356 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 357 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 358 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 359 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 360 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 361 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 362 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 363 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 364 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 365 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 366 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 367 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 368 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 369 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 370 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 371 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 372 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 373 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 374 | -------------------------------------------------------------------------------- /key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbI4qo59MJpj0J7cGLRB 3 | agSZKbnlUpllfvzstI96Z1h5eviv5Bq+HpawQL2PiYDSgQ3ys/3/hV5R5nGZ3NsO 4 | jUCm+MVPYv9kwUawodYS9HD36AP38ifLFqvF/f/bGSHSyrd2VDUiYCLpA2wPswUw 5 | mLYJF6MOwpD17i+ENNx+whvs8KWDI0GDvY1TlEEPJYt6p+sjfhfXdyLtni3QMN7f 6 | 2ieWUmc6QwONT8QnyzQEAC+yX3LSz1GkrQeff/bcdTV2VjaA+Fp3KaH8MS+Jgiug 7 | qTyAynGtdi87lJYgcjzCyhp6PvW4IOpx96YzhcJsyBjImYGwfYmAVAsOnaFbQ2QU 8 | hQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /spamc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | import io 6 | import tempfile 7 | import subprocess 8 | import logging 9 | 10 | emailfile = sys.argv[1] 11 | ftmp = tempfile.NamedTemporaryFile(delete=False) 12 | 13 | try: 14 | email = io.open(emailfile, mode='r', buffering=-1) 15 | res = subprocess.call(["spamc"], stdout=ftmp, stderr=sys.stderr, 16 | stdin=email) 17 | ftmp.close() 18 | if res != 0: 19 | logging.error("tempfile %s" % ftmp.name) 20 | raise Exception("spamc terminated with non zero") 21 | 22 | os.replace(ftmp.name, emailfile) 23 | exit(0) 24 | except Exception as e: 25 | raise e 26 | exit(1) 27 | -------------------------------------------------------------------------------- /systemd/auth.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SMTP Authentication Service 3 | 4 | [Service] 5 | ExecStart=/usr/local/sbin/auth 6 | Restart=always 7 | -------------------------------------------------------------------------------- /systemd/forwarding.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SMTP Forwarding Service 3 | 4 | [Service] 5 | ExecStart=/usr/local/sbin/forwarding 6 | Restart=always 7 | -------------------------------------------------------------------------------- /systemd/frontline.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Email & Web Frontline Services 3 | 4 | [Service] 5 | ExecStart=/usr/local/sbin/frontline-nginx -c /etc/mailway/frontline/nginx.conf 6 | Restart=always 7 | -------------------------------------------------------------------------------- /systemd/maildb.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mail Database Service 3 | 4 | [Service] 5 | ExecStart=/usr/local/sbin/maildb 6 | Restart=always 7 | -------------------------------------------------------------------------------- /systemd/mailout.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mail Outgoing Gateway 3 | 4 | [Service] 5 | ExecStart=/usr/local/sbin/mailout 6 | Restart=always 7 | -------------------------------------------------------------------------------- /systemd/mailway-supervisor.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mailway supervisor 3 | 4 | [Service] 5 | ExecStart=/usr/local/sbin/mailway supervisor 6 | Restart=always 7 | -------------------------------------------------------------------------------- /systemd/webhooks.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Webhooks service 3 | 4 | [Service] 5 | ExecStart=/usr/local/sbin/webhooks 6 | Restart=always 7 | --------------------------------------------------------------------------------