├── .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 |
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 |
--------------------------------------------------------------------------------