Skip to content

M25 — Sending abuse reports: a send queue, and optional SMTP

Status: accepted. Changes a decision of M22 (“the stack never sends mail and stores no mail credentials”): the owner asked for optional sending. Default behaviour is unchanged: nothing is sent until the owner configures SMTP in the dashboard and picks a sending mode.

A “To send” view on the Abuse reports page walks through the reports that are ready (guard passed, settings complete, status draft or reviewed), one at a time:

  • recipient, subject and body with copy buttons;
  • “Open in mail app”: a mailto: link with recipient and subject, and the body when the whole link stays under 1,800 characters. The full plain-text body is copied to the clipboard on click in every case, so a long report is one paste away;
  • “Sent, next” marks it sent (existing route) and opens the next one; keyboard shortcuts;
  • a reminder on top: connect to your mailbox through your VPN and check the sender address. Help explains how to make Proton Mail the browser’s handler for email links.

Free Proton addresses have no SMTP access: the queue is the way to use them.

2.1 Settings (Settings > Reporting > Sending)

Section titled “2.1 Settings (Settings > Reporting > Sending)”

One profile, stored in table report_smtp (single row, migration 0022): host, port, security (starttls|ssl), username, from_address, from_name, password (AES-GCM with the existing SecretBox, never returned), mode, auto_hold_minutes, daily_cap, updated_at, updated_by.

  • Write routes (PUT, DELETE /api/v1/abuse/smtp) are admin-only, CSRF, and ask for the password again. GET returns everything except the password (password_set: bool).
  • host is a DNS name (no IP literal, no internal name); port 1-65535; plain text is not an option. A “Proton (paid plan, custom domain)” preset fills smtp.protonmail.ch, 587, starttls, and says that the password is an SMTP token from Proton’s settings.
  • from_name defaults to empty. from_address and from_name go through the identity guard like every report (a term that points to the owner refuses the profile with a guard code).
modebehaviour
off (default)nothing is sent; the queue of section 1 only
clicka “Send now” button on a ready report sends it after a confirmation dialog
autoready reports are sent automatically after auto_hold_minutes (default 60, 10 to 1440); while waiting they show “Sending at hh:mm” with Cancel. At most daily_cap (default 20, 1 to 200) per UTC day

Demo reports are never sent. A report whose guard fails, or with incomplete settings, is never sent in any mode.

The API never talks SMTP (it has no route out). It writes a row in abuse_send_requests (id, report_id NULL for a test, kind: send|test, requested_by, requested_at, status: pending|sent |failed|cancelled, error_code, done_at). The ingest abuse worker:

  1. waits while the egress gate is closed (the request stays pending);
  2. re-renders the report from the stored evidence and runs the identity guard on the complete message, headers included; any failure → failed with the guard code, nothing sent;
  3. sends through suckerpot_egress (TunnelSMTP, the VPN proxy), TLS with certificate checks;
  4. on success sets the report to sent (sent_via = 'smtp', sent_at), on failure keeps it ready and records a short error code (auth_failed, tls_failed, refused, timeout, egress_closed, guard_failed).

Message hygiene: From: from_name <from_address>, To: the abuse mailbox only, no Cc/Bcc, the rendered Subject, Date in UTC, a Message-ID on the sender’s domain with a random local part, no X-Mailer/User-Agent, the X-ARF parts as rendered by M22. The SMTP EHLO name is never the container’s host name: it is the domain of from_address. The same EHLO rule is applied to the alerts’ SMTP client.

“Send a test” sends a short neutral message to from_address itself.

abuse.smtp.update, abuse.smtp.delete, abuse.send (report id, mode), abuse.send.cancel, abuse.smtp.test. Never the password, never the message body.