mailpot - mailing list manager
core
the library
cli
a command line tool to manage lists
web
an axum
based web server capable of serving archives and authenticating list owners and members
archive-http
static web archive generation or with a dynamic http server
rest-http
a REST http server to manage lists
- easy setup
- extensible through Rust API as a library
- basic management through CLI tool
- optional lightweight web archiver (static and dynamic)
- useful for both newsletters, communities and for static article comments
- extensible through HTTP REST API as an HTTP server, with webhooks
Create a configuration file and a database:
$ mkdir -p /home/user/.config/mailpot |
$ export MPOT_CONFIG=/home/user/.config/mailpot/config.toml |
$ cargo run --bin mpot -- sample-config > "$MPOT_CONFIG" |
$ # edit config and set database path e.g. "/home/user/.local/share/mailpot/mpot.db" |
$ cargo run --bin mpot -- -c "$MPOT_CONFIG" list-lists |
No lists found. |
This creates the database file in the configuration file as if you executed the following:
$ sqlite3 /home/user/.local/share/mailpot/mpot.db < ./core/src/schema.sql |
% mpot help |
GNU Affero version 3 or later <https://www.gnu.org/licenses/> |
|
Tool for mailpot mailing list management. |
|
Usage: mpot [OPTIONS] <COMMAND> |
|
Commands: |
sample-config |
Prints a sample config file to STDOUT |
dump-database |
Dumps database data to STDOUT |
list-lists |
Lists all registered mailing lists |
list |
Mailing list management |
create-list |
Create new list |
post |
Post message from STDIN to list |
flush-queue |
Flush outgoing e-mail queue |
error-queue |
Mail that has not been handled properly end up in the error queue |
queue |
Mail that has not been handled properly end up in the error queue |
import-maildir |
Import a maildir folder into an existing list |
update-postfix-config |
Update postfix maps and master.cf (probably needs root permissions) |
print-postfix-config |
Print postfix maps and master.cf entry to STDOUT |
accounts |
All Accounts |
account-info |
Account info |
add-account |
Add account |
remove-account |
Remove account |
update-account |
Update account info |
repair |
Show and fix possible data mistakes or inconsistencies |
help |
Print this message or the help of the given subcommand(s) |
|
Options: |
-d, --debug |
Print logs |
|
-c, --config <CONFIG> |
Configuration file to use |
|
-q, --quiet |
Silence all output |
|
-v, --verbose... |
Verbose mode (-v, -vv, -vvv, etc) |
|
-t, --ts <TS> |
Debug log timestamp (sec, ms, ns, none) |
|
-h, --help |
Print help (see a summary with '-h') |
|
-V, --version |
Print version |
$ cat list-request.eml | cargo run --bin mpot -- -vvvvvv post --dry-run |
TRACE - Received envelope to post: Envelope { |
Subject: "unsubscribe", |
Date: "Tue, 04 Aug 2020 14:10:13 +0300", |
From: [ |
Address::Mailbox { |
display_name: "Mxxxx Pxxxxxxxxxxxx", |
address_spec: "exxxxx@localhost", |
}, |
], |
To: [ |
Address::Mailbox { |
display_name: "", |
address_spec: "test-announce+request@localhost", |
}, |
], |
Message-ID: "<ejduu.fddf8sgen4j7@localhost>", |
In-Reply-To: None, |
References: None, |
Hash: 12581897380059220314, |
} |
TRACE - unsubscribe action for addresses [Address::Mailbox { display_name: "Mxxxx Pxxxxxxxxxxxx", address_spec: "exxxxx@localhost" }] in list [#2 test-announce] test announcements <test-announce@localhost> |
TRACE - Is post related to list [#1 test] Test list <test@localhost>? false |
$ cat list-post.eml | cargo run --bin mpot -- -vvvvvv post --dry-run |
TRACE - Received envelope to post: Envelope { |
Subject: "[test-announce] new test releases", |
Date: "Tue, 04 Aug 2020 14:10:13 +0300", |
From: [ |
Address::Mailbox { |
display_name: "Mxxxx Pxxxxxxxxxxxx", |
address_spec: "exxxxx@localhost", |
}, |
], |
To: [ |
Address::Mailbox { |
display_name: "", |
address_spec: "test-announce@localhost", |
}, |
], |
Message-ID: "<ejduu.sddf8sgen4j7@localhost>", |
In-Reply-To: None, |
References: None, |
Hash: 10220641455578979007, |
} |
TRACE - Is post related to list [#1 test] Test list <test@localhost>? false |
TRACE - Is post related to list [#2 test-announce] test announcements <test-announce@localhost>? true |
TRACE - Examining list "test announcements" <test-announce@localhost> |
TRACE - List subscriptions [ |
ListSubscription { |
list: 2, |
address: "exxxxx@localhost", |
name: None, |
digest: false, |
hide_address: false, |
receive_duplicates: false, |
receive_own_posts: true, |
receive_confirmation: true, |
enabled: true, |
}, |
] |
TRACE - Running FixCRLF filter |
TRACE - Running PostRightsCheck filter |
TRACE - Running AddListHeaders filter |
TRACE - Running FinalizeRecipients filter |
TRACE - examining subscription ListSubscription { list: 2, address: "exxxxx@localhost", name: None, digest: false, hide_address: false, receive_duplicates: false, receive_own_posts: true, receive_confirmation: true, enabled: true } |
TRACE - subscription is submitter |
TRACE - subscription gets copy |
TRACE - result Ok( |
Post { |
list: MailingList { |
pk: 2, |
name: "test announcements", |
id: "test-announce", |
address: "test-announce@localhost", |
description: None, |
archive_url: None, |
}, |
from: Address::Mailbox { |
display_name: "Mxxxx Pxxxxxxxxxxxx", |
address_spec: "exxxxx@localhost", |
}, |
subscriptions: 1, |
bytes: 851, |
policy: None, |
to: [ |
Address::Mailbox { |
display_name: "", |
address_spec: "test-announce@localhost", |
}, |
], |
action: Accept { |
recipients: [ |
Address::Mailbox { |
display_name: "", |
address_spec: "exxxxx@localhost", |
}, |
], |
digests: [], |
}, |
}, |
) |
Using mailpot
as a library
use mailpot::{models::*, *}; |
use tempfile::TempDir; |
|
let tmp_dir = TempDir::new().unwrap(); |
let db_path = tmp_dir.path().join("mpot.db"); |
let config = Configuration { |
send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()), |
db_path: db_path.clone(), |
data_path: tmp_dir.path().to_path_buf(), |
administrators: vec!["myaddress@example.com".to_string()], |
}; |
let db = Connection::open_or_create_db(config)?.trusted(); |
|
// Create a new mailing list |
let list_pk = db.create_list(MailingList { |
pk: 0, |
name: "foobar chat".into(), |
id: "foo-chat".into(), |
address: "foo-chat@example.com".into(), |
description: None, |
topics: vec![], |
archive_url: None, |
})?.pk; |
|
db.set_list_post_policy( |
PostPolicy { |
pk: 0, |
list: list_pk, |
announce_only: false, |
subscription_only: true, |
approval_needed: false, |
open: false, |
custom: false, |
}, |
)?; |
|
// Drop privileges; we can only process new e-mail and modify subscriptions from now on. |
let mut db = db.untrusted(); |
|
assert_eq!(db.list_subscriptions(list_pk)?.len(), 0); |
assert_eq!(db.list_posts(list_pk, None)?.len(), 0); |
|
// Process a subscription request e-mail |
let subscribe_bytes = b"From: Name <user@example.com> |
To: <foo-chat+subscribe@example.com> |
Subject: subscribe |
Date: Thu, 29 Oct 2020 13:58:16 +0000 |
Message-ID: <1@example.com> |
|
"; |
let envelope = melib::Envelope::from_bytes(subscribe_bytes, None)?; |
db.post(&envelope, subscribe_bytes, /* dry_run */ false)?; |
|
assert_eq!(db.list_subscriptions(list_pk)?.len(), 1); |
assert_eq!(db.list_posts(list_pk, None)?.len(), 0); |
|
// Process a post |
let post_bytes = b"From: Name <user@example.com> |
To: <foo-chat@example.com> |
Subject: my first post |
Date: Thu, 29 Oct 2020 14:01:09 +0000 |
Message-ID: <2@example.com> |
|
Hello |
"; |
let envelope = |
melib::Envelope::from_bytes(post_bytes, None).expect("Could not parse message"); |
db.post(&envelope, post_bytes, /* dry_run */ false)?; |
|
assert_eq!(db.list_subscriptions(list_pk)?.len(), 1); |
assert_eq!(db.list_posts(list_pk, None)?.len(), 1); |
# Ok::<(), Error>(()) |