aboutsummaryrefslogtreecommitdiffstats
path: root/src/config.h
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-23 21:15:13 +0200
committerDanilo M. <danix@danix.xyz>2026-08-23 21:15:13 +0200
commitfabcf080652c6e5d57bf234be5e100769a9b965b (patch)
tree0de4222c1e2aab58c38d34c9e0e3c37c68298cc8 /src/config.h
parentc50bea78e036518ce1a2a3eb899bbb5e305affea (diff)
parentddcae8d02ef46db522b3cf6c228196c7a66a6432 (diff)
downloadqtmaildir-fabcf080652c6e5d57bf234be5e100769a9b965b.tar.gz
qtmaildir-fabcf080652c6e5d57bf234be5e100769a9b965b.zip
Merge branch 'compose-and-send': composing and sending mail
Item 123, built over 2026-08-20 to 2026-08-23 in thirteen tasks against docs/superpowers/specs/2026-08-20-compose-and-send-design.md. The application writes mail now. A composer window per message, markdown as the body, drafts autosaving into the account's Maildir, and sending through a per-account command on stdin rather than any network protocol of this program's own. A countdown with an Undo stands between pressing Send and the command running. Two things came in alongside it. The notmuch auto-tagging hooks moved here from the retiring `mailctl` project and learned that mail this application files itself never arrived, so sent mail and drafts stop appearing in the inbox. And the v1/v2 language is retired: semver on the user-visible surface is the rule, and those labels described a split that composing made obsolete. Hand tested against a fake send command rather than a real one, deliberately: New, Reply and Forward all produce correct messages, a forwarded attachment survives intact, and the sent copy is filed. That testing found the two defects fixed on this branch, and both were invisible to the suite: a composer orphaned by quitting the main window, and every sent message tagged `inbox`. Twenty-two defects were found in the plan document's own draft code while building it, which is why CLAUDE.md says to treat every code block in a plan as a draft.
Diffstat (limited to 'src/config.h')
-rw-r--r--src/config.h56
1 files changed, 56 insertions, 0 deletions
diff --git a/src/config.h b/src/config.h
index ede5dea..51fc1ee 100644
--- a/src/config.h
+++ b/src/config.h
@@ -67,6 +67,24 @@ struct Account
/// reports a missing key through the warnings path.
QString trash;
+ /// The command that sends mail from this account, receiving the complete
+ /// RFC822 message on stdin. Optional, and its ABSENCE is meaningful:
+ /// an account without one is receive-only by construction.
+ ///
+ /// Not a separate `receive_only` key. The capability IS this command's
+ /// presence, so there is nothing to keep in step and nothing to
+ /// contradict. One real account is receive-only on purpose and gains no
+ /// configuration at all, which is the point.
+ ///
+ /// Split with QProcess::splitCommand and run WITHOUT a shell, exactly as
+ /// [sync] command is, so nothing in a message body, a recipient address or
+ /// a display name can reach sh. No message content is ever placed in an
+ /// argument: recipients come from the message's own headers.
+ QString sendCommand;
+
+ /// Whether this account can send at all.
+ bool canSend() const { return !sendCommand.isEmpty(); }
+
/// The account's inbox folder, relative to maildir. Optional.
///
/// Only Restore reads it, as the destination for a message that carries no
@@ -186,6 +204,35 @@ struct SavedQuery
QJsonObject unknown;
};
+/// The [compose] section. Every key is optional with the default shown.
+struct ComposeSettings
+{
+ /// Where the quote goes in a reply. Whether to quote AT ALL is not here:
+ /// that is decided by which action was invoked (reply quotes,
+ /// reply_no_quote does not).
+ enum class QuotePosition { Above, Below };
+
+ QuotePosition quotePosition = QuotePosition::Above;
+
+ /// Seeds the per-message toggle for New and Forward only. Reply and
+ /// Reply-all seed from whether the original carried a text/html part,
+ /// ignoring this value: an HTML part in the original is a fact about the
+ /// sender's software, not a guess about their taste.
+ bool sendHtml = true;
+
+ int autosaveIntervalMs = 30000;
+
+ /// The undo window before sending. Zero skips the countdown entirely and
+ /// sends at once, for anyone who finds it irritating.
+ int sendDelayMs = 5000;
+
+ /// Preferred account for a New message when the dropdown is on All
+ /// accounts. Falls through when it names an account that cannot send.
+ QString defaultAccount;
+
+ qint64 attachmentWarnBytes = 26214400;
+};
+
/// Reads ~/.config/qtmaildir/qtmaildir.conf.
///
/// The Maildir path is deliberately NOT configurable here: notmuch already
@@ -207,6 +254,14 @@ public:
QList<Account> accounts() const { return m_accounts; }
Account account(const QString &key) const;
+ ComposeSettings compose() const { return m_compose; }
+
+ /// Every account with a send_command, in configuration order.
+ ///
+ /// Empty is a valid read-only installation, NOT a misconfiguration: the
+ /// compose actions are simply disabled and nothing is warned about.
+ QList<Account> sendingAccounts() const;
+
/// In document order, which IS the display order. Never sort this.
QList<SavedQuery> savedQueries() const { return m_savedQueries; }
void setSavedQueries(const QList<SavedQuery> &queries)
@@ -443,6 +498,7 @@ private:
QList<Account> m_accounts;
QList<SavedQuery> m_savedQueries;
+ ComposeSettings m_compose;
/// Where saveSavedQueries() writes, remembered from load().
QString m_queriesPath;