aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md61
1 files changed, 61 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0aa78f7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+# ingest
+
+Recieve HTTP-enveloped mail and route it as LMTP to local Unix socket.
+
+Cloudflare Email Workers can only reach a cf tunnel over HTTP, so the
+message has to be wrapped in an HTTP request to get through. On the
+server side, `ingest` unwraps it and hands it to an LMTP-capable MDA
+such as Dovecot.
+
+## Quickstart
+
+ cat <<EOF >/etc/ingest.env
+ INGEST_TOKEN=$(uuidgen)
+ LMTP_SOCK=/var/run/dovecot/lmtp
+ LISTEN_ADDR=127.0.0.1:1234
+ RUST_LOG=info
+ EOF
+
+ cargo build --release
+ cp target/release/ingest /usr/local/bin/
+ cp mail-ingest.service /etc/systemd/system/
+ systemctl daemon-reload
+ systemctl enable --now mail-ingest.service
+
+## Example client
+```ts
+export default {
+ async email(message, env, ctx) {
+ const res = await fetch("https://example.com/inject", {
+ method: "POST",
+ headers: {
+ "authorization": `Bearer ${env.INGEST_TOKEN}`,
+ "content-type": "message/rfc822",
+ "x-envelope-from": message.from,
+ "x-envelope-to": message.to,
+ },
+ body: message.raw,
+ });
+
+ if (!res.ok) {
+ const detail = await res.text().catch(() => "");
+ throw new Error(`ingest ${res.status} ${detail.slice(0, 200)}`);
+ }
+ },
+};
+```
+
+## Response codes
+
+| Status | Meaning | Client action |
+|---|---|---|
+| `204 No Content` | Message accepted by the MDA | Done |
+| `401 Unauthorized` | Missing or wrong bearer token | Fix config; do not retry |
+| `413 Payload Too Large` | Body exceeds 26 MiB | Do not retry |
+| `422 Unprocessable Entity` | LMTP replied with a permanent error (5xx), e.g. unknown recipient | Do not retry; bounce |
+| `503 Service Unavailable` | LMTP unreachable or replied with a temporary error (4xx) | Retry later |
+
+`ingest` itself never retries. When a Worker throws on a non-2xx
+response, Cloudflare returns a temporary error to the sending MTA,
+which keeps the message in the sender's queue for a few days. (This
+behaviour is not documented by Cloudflare; verified against Gmail.)