Cursed IMAP forwarding service
Go to file
Amelia Cuss 44a2b6ec54 Cargo.toml: downgrade to clap that we can build. 2024-03-05 13:04:28 +11:00
src idle: timeout in 1min, not 10min. 2024-01-29 13:48:12 +11:00
.gitignore flake.nix: add package, sketch nixosModule. 2023-09-17 18:23:58 +10:00
CHANGELOG.md script: recipient patterns are case-insensitive. 2023-09-20 15:33:01 +10:00
Cargo.lock Cargo.toml: downgrade to clap that we can build. 2024-03-05 13:04:28 +11:00
Cargo.toml Cargo.toml: v0.1.2-pre. 2023-09-20 14:47:09 +10:00
LICENSE.txt license: update. 2023-11-07 17:55:57 +11:00
README.md README: link to canonical source. 2024-01-18 17:11:42 +11:00
config.toml.sample config: extract folders. 2023-10-05 17:56:45 +11:00
flake.lock flake.nix: fix devShell nonsense. 2023-09-17 20:14:07 +10:00
flake.nix config: extract folders. 2023-10-05 17:56:45 +11:00

recogedor

Cursed IMAP forwarding service.

todo

  • harden against disconnects etc.
  • ensure unsolicited EXISTS are picked up/check EXISTS races
  • clean shutdown
  • obviously optimizable part: if x { flag y } else { flag z } => CTE.

install

Add the repository to your flake inputs:

inputs.recogedor.url = github:kivikakk/recogedor;

Add the NixOS module exposed by the flake:

nixosSystem {
  # ...
  modules = [
    # ...
    recogedor.nixosModules.${system}.default
  ];
}

Configure your system to use recogedor:

services.recogedor = {
  enable = true;
  settings = lib.importTOML ./recogedor.toml;
};

config

Configure a source and one or more destination mailboxes. imap is currently the only supported type. TLS is always used. host is used for SNI. The ip can be specified manually.

The source defines the list of folders to monitor. Mail items are appended to the corresponding folder on the destination side.

The process script is a Lisp. I'm terribly sorry. One or more sexps define the action to be taken on each mail item. You must implement your own idempotency method. Recogedor will rescan the entire source INBOX on startup and every time it's woken from IDLE.

Builtin names are unadorned symbols. Flags, recipient patterns, and destinations are strings. Statement and condition forms are cons cells where the car identifies the builtin.

The following statement forms are defined:

  • (if C T E) -- evaluate the condition C and execute statement T if true, E otherwise. E may be omitted.
  • (do ...) -- execute the statements following.
  • (halt!) -- stop processing this mail item.
  • (append! D) -- append this mail item to destination D.
  • (flag! F) -- set the flag F on the mail item.
  • (delete!) -- delete the mail item on the source.

The following condition forms are defined:

  • (or C*) -- true if any C is true.
  • (flagged F) -- true if the mail item has the flag F.
  • (received-by R) -- true if any recipient in the mail item's envelope matches the recipient pattern R.

Recipient patterns consist of an optional user part, an optional plus part, and an optional host part. At least one part must be specified. A recipient matches a recipient pattern if all parts defined in the pattern are case-insensitive equal to the corresponding parts of the recipient. The + character is not considered part of the plus part.

The syntax is roughly defined as follows: (user)?(+plus)?@(host)?. Note that a recipient pattern always contains an @ symbol. Examples follow:

  • abc@def.com -- matches abc@def.com and ABC+X@DEF.COM.
  • +@def.com -- matches abc@def.com and xyz@def.com. Does not match a+b@def.com.
  • +debug@ -- matches hello+debug@world.com and x+debug@i.net.
  • support@ -- matches support@nyonk and support+123@shomk.

example

Forward new mail received on one Fastmail account with multiple aliases to two different local accounts. Mail is flagged to avoid double handling. Mail is flagged after appending to fail "safe" -- an untimely power outage will result in double appending, not zero appending.

[src]
type = "imap"
host = "imap.fastmail.com"
port = 993
user = "fox@den.com"
pass = "abc123"
folders = ["INBOX", "Spam"]

[dest.fox]
type = "imap"
host = "my.mx.com"
ip = "127.0.0.1"
port = 993
user = "fox@den.com"
pass = "ghi789"

[dest.wolf]
type = "imap"
host = "my.mx.com"
ip = "127.0.0.1"
port = 993
user = "wolf@den.com"
pass = "jkl012"

[process]
script = """
  (if (flagged "Recogido") (halt!))
  (if
    (or
      (received-by "fox@den.com")
      (received-by "s.fox@foxden.net"))
    (do
      (append! "fox")
      (flag! "Recogido"))
    (do
      (append! "wolf")
      (delete!)))
"""

development

  • Unit tests? Just design and write it correctly the first time.

legal

Copyright (c) 2023, Asherah "Charlotte" Connor.
Licensed under the Zero-Clause BSD License.