Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Attachments

Files can go with a notification to services that support them: e-mail, Discord, Slack, Telegram, Matrix, Pushover, ntfy, the json://, form:// and xml:// webhooks and many more. The services index has an “Attachments” column and every service page says yes or no. A service without attachment support still gets the text of the message; a message with attachments but no body fails for such a service.

Sources

SourceExample
Local path/var/log/backup.log, ./report.pdf
file:// URLfile:///var/log/backup.log
http://, https://https://example.com/report?id=7, https://user:pass@cam.local/snapshot.jpg
memory://in-memory content (library); a URL only names it

Remote files are downloaded through the notifier’s transport before sending, so FakeTransport covers them in tests and a custom transport applies to them. Credentials in the URL authenticate the download; treat such URLs as secrets.

URL options

OptionMeaning
?name=report.pdffile name the recipient sees (base name only)
?mime=image/pngMIME type; must be a known type
?cache=yes|no|SECONDShow long a loaded copy is reused (default 600 s); no or 0 reloads every time
?verify=no(http) skip TLS certificate verification
?cto=, ?rto=(http) connect and read timeouts
?+Header=value(http) extra request header
anything else(http) forwarded to the server as a query parameter

Without ?name=, the name comes from the Content-Disposition header, then the URL path, then mercurio-attachment.<ext> (the extension guessed from the MIME type). Without ?mime=, the type comes from the Content-Type header, then the file extension, then application/octet-stream.

Size limit

Asset::attachment_max_size caps each attachment: 1000 MiB by default, 0 for no limit. The check uses the file size, the Content-Length header and the downloaded body.

Command line

mercurio -t 'Nightly backup' -b 'See the log' \
  -a /var/log/backup.log \
  -a 'https://example.com/report?id=7&name=report.pdf' \
  'mailto://user:pass@example.com'

-a is repeatable. A file that cannot be loaded is an error before anything is sent.

The HTTP server accepts multipart file parts and attach fields holding http(s):// URLs; a local path or file:// is rejected with HTTP 400, so clients cannot read files on the server.

Library

Message::attach takes an in-memory Attachment (name, MIME type, bytes). AttachmentSet parses the sources above and loads them:

#![allow(unused)]
fn main() {
use mercurio::testing::FakeTransport;
use mercurio::{attachment::AttachSource, AttachmentSet, Asset, Attachment, Message, transport::Response};

let fake = FakeTransport::new();
fake.respond_with(Response::new(200, "a,b\n1,2\n").with_header("Content-Type", "text/csv"));

let mut set = AttachmentSet::new();
set.add("https://example.com/export?id=7&name=readings.csv")?;
set.push(AttachSource::text("generated in memory", Some("notes.txt")))?;

let mut msg = Message::new("Nightly export")
    .attach(Attachment::new("hello.bin", "application/octet-stream", vec![1, 2, 3]));
msg.attachments.extend(set.load(&fake, &Asset::default())?);

let names: Vec<&str> = msg.attachments.iter().map(|a| a.name.as_str()).collect();
assert_eq!(names, ["hello.bin", "readings.csv", "notes.txt"]);
assert_eq!(msg.attachments[1].mime, "text/csv");
// `name=` is an option, not part of the download URL.
assert_eq!(fake.requests()[0].url, "https://example.com/export?id=7");
Ok::<(), mercurio::Error>(())
}

Loading stops at the first failure.

When a message is split, the attachments go with the first piece; with overflow=truncate only the first attachment is sent.