From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp0.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 SK7bAIuMDma/CAEAqHPOHw:P1 (envelope-from ) for ; Thu, 04 Apr 2024 13:18:35 +0200 Received: from aspmx1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp0.migadu.com with LMTPS id SK7bAIuMDma/CAEAqHPOHw (envelope-from ) for ; Thu, 04 Apr 2024 13:18:35 +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=1712229514; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type:list-id: list-help:list-owner:list-unsubscribe:list-subscribe:list-post; bh=eeur5ADxUOinJYAVFEgHDmnkmiVVfmfSNUOCRwbUyRc=; b=CTnsJUBUGGQQR+Oag6oghhlBA8DNpeCPx0lIw+9XuEWEnnpORSX7ruVJ4PX+ugwZbgQ2Qg N+YkF3fgkBumiUJZ8PHYXVWRt46lYsrqGMpOy9EEAzLLd+et+IffJ5wCrMAsdgewzpGQtU IdEWH54Cuk4eAStOAyN8OxIwdlX3hqZmtX2lC67l8Vr5SdZz9aABZ+ebtiEWNulCw2GjkR L2Pphc+CVq/cJZc9ASbqLW5RFKqs5VFXnfCRQvqM4efLsuVnZLZGGJ4jGakd/lF4p6yshk 6VaroNSe/ENDbX+w17Ma0O89PuGCI1gnWrviGMVQGcMcAWPDXRtfGHVmkPxzzA== 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=1712229514; a=rsa-sha256; cv=none; b=AuXzRDqAgeONvnOdS8rHlUNdfxworrTZ4cQfbfKewa25SOrU1jdb/kvo5R6Ph1Lu85hyRM no6qgw3WC7az4CCHXayVNIuE+ROQhMJLUusbQjjksYX195FxijQURPdQKnO+KACFnuLMTI nRMF2DtydcJrKS7x66FW0hbqvugfgDHxFkpkdVxqFUMNfAoMG/N5inIT9U3+YS6BIODVNB nB+UkFn50GxWLCyOP2P/eYFE6CKQqMsI4KBgbx6PNWSQEZgxOP3ezS7QtrR38+rcsvuIju sJy3iCX2CxFEpwZzn+4SsweYZ0ISKBJ11Lcs+X09g+XBbS0/j34uu4PSFIuFSg== 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 BBE6166D98 for ; Thu, 04 Apr 2024 13:18:34 +0200 (CEST) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id 35A6A5F82C; Thu, 4 Apr 2024 11:18:26 +0000 (UTC) Received: from 3.mo580.mail-out.ovh.net (3.mo580.mail-out.ovh.net [178.33.255.153]) by mail.notmuchmail.org (Postfix) with ESMTPS id 878555F6BC for ; Thu, 4 Apr 2024 11:18:23 +0000 (UTC) Received: from mxplan8.mail.ovh.net (unknown [10.109.139.13]) by mo580.mail-out.ovh.net (Postfix) with ESMTPS id 4V9JnD3y52z18Cr; Thu, 4 Apr 2024 11:10:24 +0000 (UTC) Received: from telenieko.com (37.59.142.102) by mxplan8.mail.ovh.net (172.16.2.1) 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 13:10:23 +0200 X-OVh-ClientIp: 80.28.217.91 From: Marc Fargas To: , Subject: Re: Scheduling mails User-Agent: Notmuch/0.38.3 (https://notmuchmail.org) Emacs/29.3 (x86_64-pc-linux-gnu) Date: Thu, 4 Apr 2024 13:10:21 +0200 Message-ID: <87le5to02q.fsf@marcfargas.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Ovh-Tracer-GUID: 87da9637-75af-4757-ae6c-87392c3573f5 X-Ovh-Tracer-Id: 9988983976033521117 X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: 0 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedvledrudefkedgfeejucetufdoteggodetrfdotffvucfrrhhofhhilhgvmecuqfggjfdpvefjgfevmfevgfenuceurghilhhouhhtmecuhedttdenucenucfjughrpefhvffufgffkfggtgesmhdtreertdertdenucfhrhhomhepofgrrhgtucfhrghrghgrshcuoehtvghlvghnihgvkhhosehtvghlvghnihgvkhhordgtohhmqeenucggtffrrghtthgvrhhnpeetledvueegjefhgfehffdtgeeiueeitedvgeekvdeghfdvkeeiueeitddufffggeenucfkphepuddvjedrtddrtddruddpkedtrddvkedrvddujedrledupdefjedrheelrddugedvrddutddvnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehinhgvthepuddvjedrtddrtddruddpmhgrihhlfhhrohhmpehtvghlvghnihgvkhhosehtvghlvghnihgvkhhordgtohhmpdhnsggprhgtphhtthhopeefpdhrtghpthhtohepnhhothhmuhgthhesnhhothhmuhgthhhmrghilhdrohhrghdprhgtphhtthhopehjphgvughrohguvggrmhhorhhimhesghhmrghilhdrtghomhdprhgtphhtthhopehtvghlvghnihgvkhhoodhftggtsehgmhgrihhlrdgtohhmpdfovfetjfhoshhtpehmohehkedtpdhmohguvgepshhmthhpohhuth Message-ID-Hash: CGTBAGDEYXKD2CP6HO65XAMLESDOH27Q X-Message-ID-Hash: CGTBAGDEYXKD2CP6HO65XAMLESDOH27Q 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 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: -0.48 X-Migadu-Scanner: mx11.migadu.com X-Spam-Score: -0.48 X-Migadu-Queue-Id: BBE6166D98 X-TUID: dwjxUyvV9ibn --=-=-= Content-Type: text/plain 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 --=-=-=--