From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms1.migadu.com with LMTPS id eF8XNKjXDmZJSQEAe85BDQ:P1 (envelope-from ) for ; Thu, 04 Apr 2024 18:39:05 +0200 Received: from aspmx1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2.migadu.com with LMTPS id eF8XNKjXDmZJSQEAe85BDQ (envelope-from ) for ; Thu, 04 Apr 2024 18:39:04 +0200 X-Envelope-To: larch@yhetil.org Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 135.181.149.255 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1712248744; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references:list-id:list-help: list-owner:list-unsubscribe:list-subscribe:list-post; bh=Faoh/hIxQAAUYjKJoKdaKW14BzH5+5RjTqBulVrx/uE=; b=W/E2yHFnxlq5GlXSti6OIQ0GW7Bo3f1eL/34D+cGxpoyc9lhke0nM2dLb2vQFllRaflrFg S5HQRlrTHq01Zn2b0CH5bEezMks//bXTeKkie8Lxc65OVo+sQtISaZx8O2x8pz/FQXa2Y+ a8MtdM7lyVclFnil6y/IE8BNPc8BwWqehMubUABxJM8sD1Xt7NfUsprHrk0k/6s7i/2Rj3 0IqKqxQ1RGjK1IgTht/ubM7a5GUEs0xr1eOFVZBbfLcmV/S8J5ZmPkJETNGn0a7CNxQlQM +I/ls4JkV8GRTg/xLSgC81MwYJEflhIIt/oCnFTYpjK3z6Qz0Zcx8RIAjm+FZQ== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 135.181.149.255 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org ARC-Seal: i=1; s=key1; d=yhetil.org; t=1712248744; a=rsa-sha256; cv=none; b=M7I6XaD8OBW42nuvBcyWjan7ewYrRNPrLuoE/KMy7lO1sMboj36mDyHieD50HRe/+GSBSH 77MkaP7IRkKbE9R4DnDWTS3MG0kq8UbIR1qkNPcfy7OYbHChnr+tmoQc5ff9N3vzaZxcVH pkCuIKIlr1tSq2OTRsEP47DaVaDXIqLxK1QyA6/hsqRnAf6P62KxSE8r1QgMSBdzceNNB+ 2lPflqWyVLW98vq1IzIzkZ5jzSjUIz072z2cM6GYWiWoubNxX7wAmrk6KWNzEtl9RvK54C 3kqRtrmenpRC2dpaD/xGXInHlKMz/SjlSaO8dylVuJ0TCoZ2IYkUFXodABibvA== Received: from mail.notmuchmail.org (yantan.tethera.net [135.181.149.255]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 8527166DB5 for ; Thu, 04 Apr 2024 18:39:04 +0200 (CEST) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id 7E77B5F826; Thu, 4 Apr 2024 16:38:56 +0000 (UTC) Received: from 7.mo579.mail-out.ovh.net (7.mo579.mail-out.ovh.net [46.105.47.152]) by mail.notmuchmail.org (Postfix) with ESMTPS id EE6DF5F6BC for ; Thu, 4 Apr 2024 16:38:53 +0000 (UTC) Received: from mxplan8.mail.ovh.net (unknown [10.108.9.1]) by mo579.mail-out.ovh.net (Postfix) with ESMTPS id 4V9S4F3RX8z1HpK; Thu, 4 Apr 2024 16:38:53 +0000 (UTC) Received: from telenieko.com (37.59.142.108) by mxplan8.mail.ovh.net (172.16.2.2) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.37; Thu, 4 Apr 2024 18:38:52 +0200 X-OVh-ClientIp: 80.28.217.91 From: Marc Fargas To: Subject: Re: Scheduling mails In-Reply-To: <87frw4sjkd.fsf@ergo> References: <87frw4sjkd.fsf@ergo> User-Agent: Notmuch/0.38.3 (https://notmuchmail.org) Emacs/29.3 (x86_64-pc-linux-gnu) Date: Thu, 4 Apr 2024 18:38:52 +0200 Message-ID: <878r1tnkv7.fsf@marcfargas.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Ovh-Tracer-GUID: 0d20ac33-6460-45de-aacd-f732bda5d73f X-Ovh-Tracer-Id: 15536574292754107802 X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: 0 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedvledrudefkedguddtgecutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfqggfjpdevjffgvefmvefgnecuuegrihhlohhuthemucehtddtnecunecujfgurhephffvvefujghffgffkfggtgesmhdtreertdertdenucfhrhhomhepofgrrhgtucfhrghrghgrshcuoehtvghlvghnihgvkhhosehtvghlvghnihgvkhhordgtohhmqeenucggtffrrghtthgvrhhnpeeggeekieeitddugedtheevffehjedvleetvdfgleeljedtffehteduhfekuedvjeenucfkphepuddvjedrtddrtddruddpkedtrddvkedrvddujedrledupdefjedrheelrddugedvrddutdeknecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehinhgvthepuddvjedrtddrtddruddpmhgrihhlfhhrohhmpehtvghlvghnihgvkhhosehtvghlvghnihgvkhhordgtohhmpdhnsggprhgtphhtthhopedvpdhrtghpthhtohepjhhpvggurhhouggvrghmohhrihhmsehgmhgrihhlrdgtohhmpdhrtghpthhtohepnhhothhmuhgthhesnhhothhmuhgthhhmrghilhdrohhrghdpoffvtefjohhsthepmhhoheejledpmhhouggvpehsmhhtphhouhht Message-ID-Hash: YXJOGPAIMXJYXS4FQLDSPXCLSMCHY45G X-Message-ID-Hash: YXJOGPAIMXJYXS4FQLDSPXCLSMCHY45G X-MailFrom: telenieko@telenieko.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-notmuch.notmuchmail.org-0 CC: notmuch@notmuchmail.org X-Mailman-Version: 3.3.3 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Migadu-Flow: FLOW_IN X-Migadu-Country: DE X-Migadu-Spam-Score: -1.65 X-Migadu-Scanner: mx11.migadu.com X-Spam-Score: -1.65 X-Migadu-Queue-Id: 8527166DB5 X-TUID: zwYzgbDLbzyv --=-=-= Content-Type: text/plain Content-Disposition: inline Hi there, > I might be wrong, but I don't think the Gnus agent can be easily reused > from notmuch. We could perhaps save the message as a draft upon C-c C-j, > and then have a periodic timer that checks if any of the drafts has > expired (maybe using a special, additional tag for the search) and send > it? I have solved this myself using `gnus-delay-article` as a basis. I am attaching here my current code, it is my intention to (time permitting) turn this over a patch for inclusion into notmuch itself. What is preventing such patch right now: - Writting tests, - I'm trying to find something like `killing-new-buffers` builtin but will probably just include the code. A side of the attached file you will need the `killing-new-buffers` macro, pasted below. (defmacro killing-new-buffers (&rest body) "Run BODY and kill any buffers that were not already open." (declare (debug t)) (cl-with-gensyms (initial-buffers) `(let ((,initial-buffers (buffer-list))) (unwind-protect ,(macroexp-progn body) (dolist (b (buffer-list)) (unless (memq b ,initial-buffers) (kill-buffer b))))))) Hope this helps, marc --=-=-= Content-Type: application/emacs-lisp; charset="utf-8" Content-Disposition: attachment; filename="notmuch-delay.el" Content-Transfer-Encoding: quoted-printable ;;; notmuch-delay.el --- delay sending of mails in notmuch ;; Copyright (C) 2024 Marc Fargas ;; Author: Marc Fargas ;; Maintainer: Marc Fargas ;; Version: 20240311.0 ;; URL: ??? ;; This file is not part of GNU Emacs. ;;; Commentary: ;; Provide delayed sending of e-mails. ;; Heavily inspired by gnus-delay, mu4e-delay and mu4e-send-delay, but ;; made to work with notmuch. Thanks to Ben Maughen, Kai Gro=C3=9Fjohan and ;; Benjamin Andresen. ;;; Code: (require 'notmuch) (require 'notmuch-draft) (autoload 'parse-time-string "parse-time" nil nil) (defgroup notmuch-delay nil "Arrange for sending e-emails later." :group 'notmuch) (defcustom notmuch-delay-tag "delayed" "Tag applied to delayed messages." :type 'string) (defcustom notmuch-delay-header "X-Notmuch-Delayed" "Header name for storing info about delayed messages." :type 'string) (defcustom notmuch-delay-default-delay "06:15" "Default length of delay." :type 'string) (defcustom notmuch-delay-disabled-message-send-hooks '(org-mime-confirm-whe= n-no-multipart) "`message-send-hook's that will be avoided during the delayed send. But they would be run when postponing the message." :type 'list) (defvar notmuch-delay-timer-scheduled nil "The current timer for the scheduled run.") (defvar notmuch-delay-timer-idle nil "The current timer for the idle run.") (defun notmuch-delay-parse-delay (delay) "Parse a delay string into a date. Possible values are: * for in minutes (`m'), hours (`h'), days (`d'), weeks (`w'), months (`M'), or years (`Y'); * YYYY-MM-DD for a specific date. The time of day is given by the variable `gnus-delay-default-hour', minute and second are zero. * hh:mm for a specific time. Use 24h format. If it is later than this time, then the deadline is tomorrow, else today." (let (num unit year month day hour minute deadline) ;; days (cond ((string-match "\\([0-9][0-9][0-9]?[0-9]?\\)-\\([0-9]+\\)-\\([0-9]+\\)" delay) (setq year (string-to-number (match-string 1 delay)) month (string-to-number (match-string 2 delay)) day (string-to-number (match-string 3 delay))) (setq deadline (message-make-date (encode-time 0 0 ; second and minute gnus-delay-default-hour day month year)))) ((string-match "\\([0-9]+\\):\\([0-9]+\\)" delay) (setq hour (string-to-number (match-string 1 delay)) minute (string-to-number (match-string 2 delay))) ;; Use current time, except... (setq deadline (decode-time nil nil t)) ;; ... for minute and hour. (setq deadline (apply #'encode-time (car deadline) minute hour (nthcdr 3 deadline))) ;; If this time has passed already, add a day. (when (time-less-p deadline nil) (setq deadline (time-add 86400 deadline))) ; 86400 secs/day ;; Convert seconds to date header. (setq deadline (message-make-date deadline))) ((string-match "\\([0-9]+\\)\\s-*\\([mhdwMY]\\)" delay) (setq num (match-string 1 delay)) (setq unit (match-string 2 delay)) ;; Start from seconds, then multiply into needed units. (setq num (string-to-number num)) (cond ((string=3D unit "Y") (setq delay (* num 60 60 24 365))) ((string=3D unit "M") (setq delay (* num 60 60 24 30))) ((string=3D unit "w") (setq delay (* num 60 60 24 7))) ((string=3D unit "d") (setq delay (* num 60 60 24))) ((string=3D unit "h") (setq delay (* num 60 60))) (t (setq delay (* num 60)))) (setq deadline (message-make-date (time-add nil delay)))) (t (error "Malformed delay `%s'" delay))))) ;;;###autoload (defun notmuch-delay-message (delay) "Delay this article by some time. DELAY is a string, giving the length of the time.=20 The value of `message-draft-headers' determines which headers are generated when the article is delayed. Remaining headers are generated when the article is sent." (interactive (list (read-string "Target date (YYYY-MM-DD), time (hh:mm), or length of delay (units = in [mhdwMY]): " notmuch-delay-default-delay)) message-mode) ;; Allow spell checking etc. (run-hooks 'message-send-hook) (let ((deadline (notmuch-delay-parse-delay delay))) (message (format "delay %s" deadline)) (message-add-header (format "%s: %s" notmuch-delay-header deadline))) (set-buffer-modified-p t) (let ((notmuch-draft-tags (cons (format "+%s" notmuch-delay-tag) notmuch-= draft-tags))) (notmuch-draft-save)) (kill-buffer) (message-do-actions message-postpone-actions)) (defun notmuch-delay-get-delayed () "Get the Message-IDs of currently delayed messages. If DEADLINE is provided, return only those on which `notmuch-delay-header' = is overdue." (let* ((query (format "is:%s" notmuch-delay-tag)) (delayed (notmuch-call-notmuch-sexp "search" "--format=3Dsexp" "--format-version=3D5" "--output=3Dmessages" query))) (cl-mapcar (lambda (el) (format "id:%s" el)) delayed))) ;;;###autoload (defun notmuch-delay-send-queue () "Send all the delayed messages that are due now." (interactive) (let* ((delayed (notmuch-delay-get-delayed)) (message-send-hook (seq-difference message-send-hook notmuch-delay-disabled-message-send-hooks)) (message-confirm-send nil)) (add-hook 'message-send-hook (lambda () (message-remove-header notmuch-delay-header)) t) (save-window-excursion (killing-new-buffers (while-let ((message (pop delayed))) (notmuch-draft-resume message) (goto-char (point-min)) (if (re-search-forward (concat "^" (regexp-quote notmuch-delay-header) ":\\s-+") nil t) (progn (setq deadline-str (buffer-substring (point) (line-end-position))) (setq deadline (encode-time (parse-time-string deadline-str))) (if (time-less-p deadline nil) (progn (message "Sending delayed message %s" message) (notmuch-mua-send-and-exit) (message "Sending delayed message %s...done" message)) (message "Not yet ready to send %s until %s" message deadline-str) (kill-buffer))) (message "Delay header missing for message %s" message))))) )) (defun notmuch-delay-scheduled-runner () (unless (memq notmuch-delay-timer-idle timer-idle-list) (setq notmuch-delay-timer-idle=20 (run-with-idle-timer 30 nil #'notmuch-delay-send-queue)))) ;;;###autoload (defun notmuch-delay-initialize () (unless (memq notmuch-delay-timer-scheduled timer-list) (setq notmuch-delay-timer-scheduled (run-at-time t 300 #'notmuch-delay-scheduled-runner)))) (provide 'notmuch-delay) ;; Local Variables: ;; coding: utf-8 ;; End: ;;; notmuch-delay.el ends here --=-=-= Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline --=-=-=--