From: Marc Fargas <telenieko@telenieko.com>
To: <jpedrodeamorim@gmail.com>
Cc: notmuch@notmuchmail.org
Subject: Re: Scheduling mails
Date: Thu, 4 Apr 2024 18:38:52 +0200 [thread overview]
Message-ID: <878r1tnkv7.fsf@marcfargas.com> (raw)
In-Reply-To: <87frw4sjkd.fsf@ergo>
[-- Attachment #1: Type: text/plain, Size: 1141 bytes --]
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
[-- Attachment #2: notmuch-delay.el --]
[-- Type: application/emacs-lisp, Size: 6883 bytes --]
;;; notmuch-delay.el --- delay sending of mails in notmuch
;; Copyright (C) 2024 Marc Fargas <marc@marcfargas.com>
;; Author: Marc Fargas <marc@marcfargas.com>
;; Maintainer: Marc Fargas <marc@marcfargas.com>
;; 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ßjohan 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-when-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:
* <digits><units> for <units> 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= unit "Y")
(setq delay (* num 60 60 24 365)))
((string= unit "M")
(setq delay (* num 60 60 24 30)))
((string= unit "w")
(setq delay (* num 60 60 24 7)))
((string= unit "d")
(setq delay (* num 60 60 24)))
((string= 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.
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=sexp" "--format-version=5" "--output=messages"
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
(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
[-- Attachment #3: Type: text/plain, Size: 0 bytes --]
next prev parent reply other threads:[~2024-04-04 16:39 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <QgHQAeKcfzu4V_C0tkbpYXnzqYi9qt3ZvrB6ld7HkVYnD1tPjYaOTvwZZ9OEQVC8un-tb6uJflqJojN6lXLNJw==@protonmail.internalid>
2024-04-02 0:18 ` Scheduling mails João Pedro
2024-04-02 1:05 ` Jose A Ortega Ruiz
2024-04-02 4:34 ` João Pedro
2024-04-03 10:06 ` David Bremner
2024-04-04 2:55 ` João Pedro
2024-04-04 11:15 ` David Bremner
2024-04-04 16:38 ` Marc Fargas [this message]
2024-04-04 11:10 Marc Fargas
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://notmuchmail.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=878r1tnkv7.fsf@marcfargas.com \
--to=telenieko@telenieko.com \
--cc=jpedrodeamorim@gmail.com \
--cc=notmuch@notmuchmail.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://yhetil.org/notmuch.git/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).