mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
add example for sending email through external smtp provider
to serve as documentation. based on issue #105.
This commit is contained in:
parent
fce3a5bf73
commit
3f5823de31
3 changed files with 245 additions and 123 deletions
|
@ -1239,6 +1239,54 @@ examples with "mox example", and print a specific example with "mox example
|
||||||
ResponseHeaders:
|
ResponseHeaders:
|
||||||
X-Frame-Options: deny
|
X-Frame-Options: deny
|
||||||
X-Content-Type-Options: nosniff
|
X-Content-Type-Options: nosniff
|
||||||
|
|
||||||
|
# Example transport
|
||||||
|
|
||||||
|
# Snippet for mox.conf, defining a transport called Example that connects on the
|
||||||
|
# SMTP submission with TLS port 465 ("submissions), authenticating with
|
||||||
|
# SCRAM-SHA-256-PLUS (other providers may not support SCRAM-SHA-256-PLUS, but they
|
||||||
|
# typically do support the older CRAM-MD5).:
|
||||||
|
|
||||||
|
# Transport are mechanisms for delivering messages. Transports can be referenced
|
||||||
|
# from Routes in accounts, domains and the global configuration. There is always
|
||||||
|
# an implicit/fallback delivery transport doing direct delivery with SMTP from the
|
||||||
|
# outgoing message queue. Transports are typically only configured when using
|
||||||
|
# smarthosts, i.e. when delivering through another SMTP server. Zero or one
|
||||||
|
# transport methods must be set in a transport, never multiple. When using an
|
||||||
|
# external party to send email for a domain, keep in mind you may have to add
|
||||||
|
# their IP address to your domain's SPF record, and possibly additional DKIM
|
||||||
|
# records. (optional)
|
||||||
|
Transports:
|
||||||
|
Example:
|
||||||
|
# Submission SMTP over a TLS connection to submit email to a remote queue.
|
||||||
|
# (optional)
|
||||||
|
Submissions:
|
||||||
|
# Host name to connect to and for verifying its TLS certificate.
|
||||||
|
Host: smtp.example.com
|
||||||
|
|
||||||
|
# If set, authentication credentials for the remote server. (optional)
|
||||||
|
Auth:
|
||||||
|
Username: user@example.com
|
||||||
|
Password: test1234
|
||||||
|
Mechanisms:
|
||||||
|
# Allowed authentication mechanisms. Defaults to SCRAM-SHA-256-PLUS,
|
||||||
|
# SCRAM-SHA-256, SCRAM-SHA-1-PLUS, SCRAM-SHA-1, CRAM-MD5. Not included by default:
|
||||||
|
# PLAIN. Specify the strongest mechanism known to be implemented by the server to
|
||||||
|
# prevent mechanism downgrade attacks. (optional)
|
||||||
|
|
||||||
|
- SCRAM-SHA-256-PLUS
|
||||||
|
|
||||||
|
|
||||||
|
# Snippet for domains.conf, specifying a route that sends through the transport:
|
||||||
|
|
||||||
|
# Routes for delivering outgoing messages through the queue. Each delivery attempt
|
||||||
|
# evaluates account routes, domain routes and finally these global routes. The
|
||||||
|
# transport of the first matching route is used in the delivery attempt. If no
|
||||||
|
# routes match, which is the default with no configured routes, messages are
|
||||||
|
# delivered directly from the queue. (optional)
|
||||||
|
Routes:
|
||||||
|
-
|
||||||
|
Transport: Example
|
||||||
*/
|
*/
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
|
197
examples.go
Normal file
197
examples.go
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mjl-/sconf"
|
||||||
|
|
||||||
|
"github.com/mjl-/mox/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdExample(c *cmd) {
|
||||||
|
c.params = "[name]"
|
||||||
|
c.help = `List available examples, or print a specific example.`
|
||||||
|
|
||||||
|
args := c.Parse()
|
||||||
|
if len(args) > 1 {
|
||||||
|
c.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
var match func() string
|
||||||
|
for _, ex := range examples {
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Println(ex.Name)
|
||||||
|
} else if args[0] == ex.Name {
|
||||||
|
match = ex.Get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if match == nil {
|
||||||
|
log.Fatalln("not found")
|
||||||
|
}
|
||||||
|
fmt.Print(match())
|
||||||
|
}
|
||||||
|
|
||||||
|
var examples = []struct {
|
||||||
|
Name string
|
||||||
|
Get func() string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"webhandlers",
|
||||||
|
func() string {
|
||||||
|
const webhandlers = `# Snippet of domains.conf to configure WebDomainRedirects and WebHandlers.
|
||||||
|
|
||||||
|
# Redirect all requests for mox.example to https://www.mox.example.
|
||||||
|
WebDomainRedirects:
|
||||||
|
mox.example: www.mox.example
|
||||||
|
|
||||||
|
# Each request is matched against these handlers until one matches and serves it.
|
||||||
|
WebHandlers:
|
||||||
|
-
|
||||||
|
# Redirect all plain http requests to https, leaving path, query strings, etc
|
||||||
|
# intact. When the request is already to https, the destination URL would have the
|
||||||
|
# same scheme, host and path, causing this redirect handler to not match the
|
||||||
|
# request (and not cause a redirect loop) and the webserver to serve the request
|
||||||
|
# with a later handler.
|
||||||
|
LogName: redirhttps
|
||||||
|
Domain: www.mox.example
|
||||||
|
PathRegexp: ^/
|
||||||
|
# Could leave DontRedirectPlainHTTP at false if it wasn't for this being an
|
||||||
|
# example for doing this redirect.
|
||||||
|
DontRedirectPlainHTTP: true
|
||||||
|
WebRedirect:
|
||||||
|
BaseURL: https://www.mox.example
|
||||||
|
-
|
||||||
|
# The name of the handler, used in logging and metrics.
|
||||||
|
LogName: staticmjl
|
||||||
|
# With ACME configured, each configured domain will automatically get a TLS
|
||||||
|
# certificate on first request.
|
||||||
|
Domain: www.mox.example
|
||||||
|
PathRegexp: ^/who/mjl/
|
||||||
|
WebStatic:
|
||||||
|
StripPrefix: /who/mjl
|
||||||
|
# Requested path /who/mjl/inferno/ resolves to local web/mjl/inferno.
|
||||||
|
# If a directory contains an index.html, it is served when a directory is requested.
|
||||||
|
Root: web/mjl
|
||||||
|
# With ListFiles true, if a directory does not contain an index.html, the contents are listed.
|
||||||
|
ListFiles: true
|
||||||
|
ResponseHeaders:
|
||||||
|
X-Mox: hi
|
||||||
|
-
|
||||||
|
LogName: redir
|
||||||
|
Domain: www.mox.example
|
||||||
|
PathRegexp: ^/redir/a/b/c
|
||||||
|
# Don't redirect from plain HTTP to HTTPS.
|
||||||
|
DontRedirectPlainHTTP: true
|
||||||
|
WebRedirect:
|
||||||
|
# Just change the domain and add query string set fragment. No change to scheme.
|
||||||
|
# Path will start with /redir/a/b/c (and whathever came after) because no
|
||||||
|
# OrigPathRegexp+ReplacePath is set.
|
||||||
|
BaseURL: //moxest.example?q=1#frag
|
||||||
|
# Default redirection is 308 - Permanent Redirect.
|
||||||
|
StatusCode: 307
|
||||||
|
-
|
||||||
|
LogName: oldnew
|
||||||
|
Domain: www.mox.example
|
||||||
|
PathRegexp: ^/old/
|
||||||
|
WebRedirect:
|
||||||
|
# Replace path, leaving rest of URL intact.
|
||||||
|
OrigPathRegexp: ^/old/(.*)
|
||||||
|
ReplacePath: /new/$1
|
||||||
|
-
|
||||||
|
LogName: app
|
||||||
|
Domain: www.mox.example
|
||||||
|
PathRegexp: ^/app/
|
||||||
|
WebForward:
|
||||||
|
# Strip the path matched by PathRegexp before forwarding the request. So original
|
||||||
|
# request /app/api become just /api.
|
||||||
|
StripPath: true
|
||||||
|
# URL of backend, where requests are forwarded to. The path in the URL is kept,
|
||||||
|
# so for incoming request URL /app/api, the outgoing request URL has path /app-v2/api.
|
||||||
|
# Requests are made with Go's net/http DefaultTransporter, including using
|
||||||
|
# HTTP_PROXY and HTTPS_PROXY environment variables.
|
||||||
|
URL: http://127.0.0.1:8900/app-v2/
|
||||||
|
# Add headers to response.
|
||||||
|
ResponseHeaders:
|
||||||
|
X-Frame-Options: deny
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
`
|
||||||
|
// Parse just so we know we have the syntax right.
|
||||||
|
// todo: ideally we would have a complete config file and parse it fully.
|
||||||
|
var conf struct {
|
||||||
|
WebDomainRedirects map[string]string
|
||||||
|
WebHandlers []config.WebHandler
|
||||||
|
}
|
||||||
|
err := sconf.Parse(strings.NewReader(webhandlers), &conf)
|
||||||
|
xcheckf(err, "parsing webhandlers example")
|
||||||
|
return webhandlers
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transport",
|
||||||
|
func() string {
|
||||||
|
const moxconf = `# Snippet for mox.conf, defining a transport called Example that connects on the
|
||||||
|
# SMTP submission with TLS port 465 ("submissions), authenticating with
|
||||||
|
# SCRAM-SHA-256-PLUS (other providers may not support SCRAM-SHA-256-PLUS, but they
|
||||||
|
# typically do support the older CRAM-MD5).:
|
||||||
|
|
||||||
|
# Transport are mechanisms for delivering messages. Transports can be referenced
|
||||||
|
# from Routes in accounts, domains and the global configuration. There is always
|
||||||
|
# an implicit/fallback delivery transport doing direct delivery with SMTP from the
|
||||||
|
# outgoing message queue. Transports are typically only configured when using
|
||||||
|
# smarthosts, i.e. when delivering through another SMTP server. Zero or one
|
||||||
|
# transport methods must be set in a transport, never multiple. When using an
|
||||||
|
# external party to send email for a domain, keep in mind you may have to add
|
||||||
|
# their IP address to your domain's SPF record, and possibly additional DKIM
|
||||||
|
# records. (optional)
|
||||||
|
Transports:
|
||||||
|
Example:
|
||||||
|
# Submission SMTP over a TLS connection to submit email to a remote queue.
|
||||||
|
# (optional)
|
||||||
|
Submissions:
|
||||||
|
# Host name to connect to and for verifying its TLS certificate.
|
||||||
|
Host: smtp.example.com
|
||||||
|
|
||||||
|
# If set, authentication credentials for the remote server. (optional)
|
||||||
|
Auth:
|
||||||
|
Username: user@example.com
|
||||||
|
Password: test1234
|
||||||
|
Mechanisms:
|
||||||
|
# Allowed authentication mechanisms. Defaults to SCRAM-SHA-256-PLUS,
|
||||||
|
# SCRAM-SHA-256, SCRAM-SHA-1-PLUS, SCRAM-SHA-1, CRAM-MD5. Not included by default:
|
||||||
|
# PLAIN. Specify the strongest mechanism known to be implemented by the server to
|
||||||
|
# prevent mechanism downgrade attacks. (optional)
|
||||||
|
|
||||||
|
- SCRAM-SHA-256-PLUS
|
||||||
|
`
|
||||||
|
|
||||||
|
const domainsconf = `# Snippet for domains.conf, specifying a route that sends through the transport:
|
||||||
|
|
||||||
|
# Routes for delivering outgoing messages through the queue. Each delivery attempt
|
||||||
|
# evaluates account routes, domain routes and finally these global routes. The
|
||||||
|
# transport of the first matching route is used in the delivery attempt. If no
|
||||||
|
# routes match, which is the default with no configured routes, messages are
|
||||||
|
# delivered directly from the queue. (optional)
|
||||||
|
Routes:
|
||||||
|
-
|
||||||
|
Transport: Example
|
||||||
|
`
|
||||||
|
|
||||||
|
var static struct {
|
||||||
|
Transports map[string]config.Transport
|
||||||
|
}
|
||||||
|
var dynamic struct {
|
||||||
|
Routes []config.Route
|
||||||
|
}
|
||||||
|
err := sconf.Parse(strings.NewReader(moxconf), &static)
|
||||||
|
xcheckf(err, "parsing moxconf example")
|
||||||
|
err = sconf.Parse(strings.NewReader(domainsconf), &dynamic)
|
||||||
|
xcheckf(err, "parsing domainsconf example")
|
||||||
|
return moxconf + "\n\n" + domainsconf
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
123
main.go
123
main.go
|
@ -1086,129 +1086,6 @@ domain and create the TLSA DNS records it suggests to enable DANE.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var examples = []struct {
|
|
||||||
Name string
|
|
||||||
Get func() string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"webhandlers",
|
|
||||||
func() string {
|
|
||||||
const webhandlers = `# Snippet of domains.conf to configure WebDomainRedirects and WebHandlers.
|
|
||||||
|
|
||||||
# Redirect all requests for mox.example to https://www.mox.example.
|
|
||||||
WebDomainRedirects:
|
|
||||||
mox.example: www.mox.example
|
|
||||||
|
|
||||||
# Each request is matched against these handlers until one matches and serves it.
|
|
||||||
WebHandlers:
|
|
||||||
-
|
|
||||||
# Redirect all plain http requests to https, leaving path, query strings, etc
|
|
||||||
# intact. When the request is already to https, the destination URL would have the
|
|
||||||
# same scheme, host and path, causing this redirect handler to not match the
|
|
||||||
# request (and not cause a redirect loop) and the webserver to serve the request
|
|
||||||
# with a later handler.
|
|
||||||
LogName: redirhttps
|
|
||||||
Domain: www.mox.example
|
|
||||||
PathRegexp: ^/
|
|
||||||
# Could leave DontRedirectPlainHTTP at false if it wasn't for this being an
|
|
||||||
# example for doing this redirect.
|
|
||||||
DontRedirectPlainHTTP: true
|
|
||||||
WebRedirect:
|
|
||||||
BaseURL: https://www.mox.example
|
|
||||||
-
|
|
||||||
# The name of the handler, used in logging and metrics.
|
|
||||||
LogName: staticmjl
|
|
||||||
# With ACME configured, each configured domain will automatically get a TLS
|
|
||||||
# certificate on first request.
|
|
||||||
Domain: www.mox.example
|
|
||||||
PathRegexp: ^/who/mjl/
|
|
||||||
WebStatic:
|
|
||||||
StripPrefix: /who/mjl
|
|
||||||
# Requested path /who/mjl/inferno/ resolves to local web/mjl/inferno.
|
|
||||||
# If a directory contains an index.html, it is served when a directory is requested.
|
|
||||||
Root: web/mjl
|
|
||||||
# With ListFiles true, if a directory does not contain an index.html, the contents are listed.
|
|
||||||
ListFiles: true
|
|
||||||
ResponseHeaders:
|
|
||||||
X-Mox: hi
|
|
||||||
-
|
|
||||||
LogName: redir
|
|
||||||
Domain: www.mox.example
|
|
||||||
PathRegexp: ^/redir/a/b/c
|
|
||||||
# Don't redirect from plain HTTP to HTTPS.
|
|
||||||
DontRedirectPlainHTTP: true
|
|
||||||
WebRedirect:
|
|
||||||
# Just change the domain and add query string set fragment. No change to scheme.
|
|
||||||
# Path will start with /redir/a/b/c (and whathever came after) because no
|
|
||||||
# OrigPathRegexp+ReplacePath is set.
|
|
||||||
BaseURL: //moxest.example?q=1#frag
|
|
||||||
# Default redirection is 308 - Permanent Redirect.
|
|
||||||
StatusCode: 307
|
|
||||||
-
|
|
||||||
LogName: oldnew
|
|
||||||
Domain: www.mox.example
|
|
||||||
PathRegexp: ^/old/
|
|
||||||
WebRedirect:
|
|
||||||
# Replace path, leaving rest of URL intact.
|
|
||||||
OrigPathRegexp: ^/old/(.*)
|
|
||||||
ReplacePath: /new/$1
|
|
||||||
-
|
|
||||||
LogName: app
|
|
||||||
Domain: www.mox.example
|
|
||||||
PathRegexp: ^/app/
|
|
||||||
WebForward:
|
|
||||||
# Strip the path matched by PathRegexp before forwarding the request. So original
|
|
||||||
# request /app/api become just /api.
|
|
||||||
StripPath: true
|
|
||||||
# URL of backend, where requests are forwarded to. The path in the URL is kept,
|
|
||||||
# so for incoming request URL /app/api, the outgoing request URL has path /app-v2/api.
|
|
||||||
# Requests are made with Go's net/http DefaultTransporter, including using
|
|
||||||
# HTTP_PROXY and HTTPS_PROXY environment variables.
|
|
||||||
URL: http://127.0.0.1:8900/app-v2/
|
|
||||||
# Add headers to response.
|
|
||||||
ResponseHeaders:
|
|
||||||
X-Frame-Options: deny
|
|
||||||
X-Content-Type-Options: nosniff
|
|
||||||
`
|
|
||||||
// Parse just so we know we have the syntax right.
|
|
||||||
// todo: ideally we would have a complete config file and parse it fully.
|
|
||||||
var conf struct {
|
|
||||||
WebDomainRedirects map[string]string
|
|
||||||
WebHandlers []config.WebHandler
|
|
||||||
}
|
|
||||||
err := sconf.Parse(strings.NewReader(webhandlers), &conf)
|
|
||||||
xcheckf(err, "parsing webhandlers example")
|
|
||||||
return webhandlers
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmdExample(c *cmd) {
|
|
||||||
c.params = "[name]"
|
|
||||||
c.help = `List available examples, or print a specific example.`
|
|
||||||
|
|
||||||
args := c.Parse()
|
|
||||||
if len(args) > 1 {
|
|
||||||
c.Usage()
|
|
||||||
}
|
|
||||||
|
|
||||||
var match func() string
|
|
||||||
for _, ex := range examples {
|
|
||||||
if len(args) == 0 {
|
|
||||||
fmt.Println(ex.Name)
|
|
||||||
} else if args[0] == ex.Name {
|
|
||||||
match = ex.Get
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(args) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if match == nil {
|
|
||||||
log.Fatalln("not found")
|
|
||||||
}
|
|
||||||
fmt.Print(match())
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmdLoglevels(c *cmd) {
|
func cmdLoglevels(c *cmd) {
|
||||||
c.params = "[level [pkg]]"
|
c.params = "[level [pkg]]"
|
||||||
c.help = `Print the log levels, or set a new default log level, or a level for the given package.
|
c.help = `Print the log levels, or set a new default log level, or a level for the given package.
|
||||||
|
|
Loading…
Reference in a new issue