1 ;;; vm-serial.el --- automatic creation of personalized message bodies
2 ;; and sending of personalized serial mails
4 ;; Copyright (C) 2000-2005 Robert Widhopf-Fenk
6 ;; Author: Robert Widhopf-Fenk
7 ;; Status: Tested with XEmacs 21.4.15 & VM 7.19
8 ;; Keywords: sending mail, default mail, multiple recipients, serial mails
9 ;; X-URL: http://www.robf.de/Hacking/elisp
12 ;; This code is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 1, or (at your option)
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program; if not, write to the Free Software
24 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 ;; Are you lazy on the one hand, but you like salutations and greetings?
33 ;; If so you got the right package here! The idea is similar to those of
34 ;; autoinsert.el, tempo.el, template.el etc., but specialized for composing
37 ;; You may want to use the following into your .vm file after adding other
38 ;; vm-mail-mode-hooks ...
40 ;; (require 'vm-serial)
41 ;; (add-hook 'vm-mail-mode-hook 'vm-serial-auto-yank-mail t)
42 ;; (define-key vm-mail-mode-map "\C-c\C-t" 'vm-serial-expand-tokens)
44 ;; and check out what happens if you reply to a message or what happens after
45 ;; specifying a recipient in the to header and typing [C-c C-t].
49 ;; Now add multiple recipients to a mail before pressing [C-c C-t] and call
50 ;; [M-x vm-serial-send-mail] in order to see what happens. If you are a
51 ;; trustful guy you may add a prefix arg [C-u].
53 ;; In order to learn more about valid tokens you should have a look at the
54 ;; documentation mail template.
56 ;; Go to an newly mail buffer add a From and To header and type:
57 ;; C-u M-x vm-serial-yank-mail RET doc RET
58 ;; M-x vm-serial-expand-tokens RET
62 ;; - mail-signature: instead of using this variable, you should use
63 ;; `vm-serial-mail-signature' with exaclty the same semantics.
67 ;; Ivan Kanis has contributed some bugfixes & enhancements.
75 (defgroup vm-serial nil
76 "Sending personalized serial mails and getting message templates."
90 (let ((feature-list '(bbdb bbdb-sc)))
93 (require (car feature-list))
95 (if (load (format "%s" (car feature-list)) t)
96 (message "Library %s loaded!" (car feature-list))
97 (message "Could not load feature %S. Related functions may not work correctly!" (car feature-list)))))
98 (setq feature-list (cdr feature-list))))
100 (defvar vm-reply-list nil)
101 (defvar vm-redistribute-list nil)
102 (defvar vm-forward-list)
104 ;;-----------------------------------------------------------------------------
105 (defcustom vm-serial-token-alist
106 '(;; standard tokens you should not change (or need not)
107 ("to" (vm-serial-get-to)
108 "to header of the mail")
110 ("sir" (vm-serial-get-name 'last)
111 "the last name of the recipient")
112 ("you" (vm-serial-get-name 'first)
113 "the first name of the recipient")
114 ("mr" (vm-serial-get-name)
115 "the full name of the recipient")
117 ("bbdbsir" (vm-serial-get-bbdb-name 'last)
118 "the last name of the recipient as returned by the BBDB")
119 ("bbdbyou" (vm-serial-get-bbdb-name 'first)
120 "the first name of the recipient as returned by the BBDB")
121 ("bbdbmr" (vm-serial-get-bbdb-name)
122 "the full name of the recipient as returned by the BBDB")
124 ("me" (user-full-name)
126 ("i" (vm-serial-get-name 'first (user-full-name))
128 ("I" (vm-serial-get-name 'last (user-full-name))
130 ("point" (and (setq vm-serial-point (point)) nil)
131 "the position of point after expanding tokens")
132 ("reply" (if (and vm-reply-list vm-serial-body-contents)
133 (insert vm-serial-body-contents))
134 "set to the message body when replying")
135 ("forward" (if (and vm-forward-list vm-serial-body-contents)
136 (insert vm-serial-body-contents))
137 "set to the message body when forwarding")
138 ("body" (if vm-serial-body-contents
139 (insert vm-serial-body-contents))
140 "set to the message body before yanking a mail template")
142 ((not vm-serial-mail-signature)
144 ((stringp vm-serial-mail-signature)
145 vm-serial-mail-signature)
146 ((eq t vm-serial-mail-signature)
147 (insert-file mail-signature-file))
148 ((functionp vm-serial-mail-signature)
149 (funcall vm-serial-mail-signature))
151 (eval vm-serial-mail-signature)))
152 "the signature obtained from `vm-serial-mail-signature'")
153 ("fifosig" (concat "-- \n"
154 (shell-command-to-string
155 (concat "cat " mail-signature-file)))
156 "a signature read from a FIFO")
158 ("hi" ("Hi" "Hello" "Dear")
159 "a randomly selected hi-style salutation")
160 ("dear" ("Lovely" "Hello" "Dear" "Sweetheart")
161 "a randomly selected dear-style salutation")
162 ("bye" ("" "Bye " "Cheers " "CU ")
163 "a randomly selected bye-style greeting")
164 ("br" ("Best regards" "Sincerly" "Yours")
165 "a randomly selected best-regards-style greeting")
166 ("babe" ("honey" "sugar pie" "darling" "babe")
167 "a randomly selected honey-style salutation")
168 ("inlove" ("In love" "Dreaming of you" "1 billion kisses")
169 "a randomly selected inlove-style greeting")
170 ("your" ("honey" "sugar pie" "darling" "babe"
171 (vm-serial-get-name 'first (user-full-name)))
172 "a randomly selected your-style greeting")
174 ("hallo" ("Hi" "Griass di" "Servus" "Hallo")
176 ("mausl" ("Mausl" "Liebling" "Schatzi" "Hallo")
178 ("ciao" ("" "Ciao " "Tschüß " "Servus " "Mach's gut " "Bis denn "
181 ("sg" ("Sehr geehrte Frau/Herr")
183 ("mfg" ("Mit freundlichen Grüßen")
184 "förmliche Verabschiedung")
186 ("salut" ("Salut" "Bonjour")
187 "Une salutation au hasard")
188 ("merci" ("Merci" "Au revoir" "A+" "Amicalement")
189 "Un au revoir au hasard")
191 "*Alist for mapping tokens to real things, i.e., strings.
192 Set this by calling `vm-serial-set-tokens'!
194 The format of each record is:
196 (TOKENNAME SEXPRESSION DOCUMENTATION)
198 TOKENNAME and DOCUMENTATION have to be strings.
200 - a list starting with a string, which might be followed by other
201 string, functions or Lisp expressions
202 - a function returning a string
203 - a Lisp expression which evaluates to a string
205 When a list starting with a string then `vm-serial-expand-tokens' will
206 randomly select one of them during expansion."
208 :type '(repeat (list (string :tag "Tagname")
209 (choice (repeat :tag "List of strings" (string))
210 (sexp :tag "SExp evaluating to a string"))
211 (string :tag "Documentation"))))
213 (defcustom vm-serial-mails-alist
224 (string-match "\\.\\(de\\|at\\|ch\\)>?$"
225 (vm-mail-mode-get-header-contents "To:")))
230 "\\.\\(de\\|at\\|ch\\)>?$"
240 "\\.\\(de\\|at\\|ch\\)>?$"
266 ;; A test mail for showing what's possible
270 A LECTURE ON VM-SERIAL
272 The `vm-serial-mails-alist' contains a list of templates and associated
273 conditions and names for these templates.
275 When doing a `vm-serial-yank-mail' it will check for the first condition
276 which matches and inserts this template. Tokens in the template are
277 expanded by the function called `vm-serial-expand-tokens'.
279 There are default tokens for various things. Tokens start with the
280 string specified in `vm-serial-cookie' which is \"$(eval vm-serial-cookie)\" followed by a
281 string matching the regexp \\([a-zA-Z][a-zA-Z0-9_-]*\\) which may be
282 enclosed by {} or a lisp expressions. The first type is a named token
283 and has to be listed in the variable `vm-serial-token-alist'. It will be
284 expanded and if evaluating to a non nil object then it is inserted. In
285 order to get just the `vm-serial-cookie' \"$(eval vm-serial-cookie)\" simply write it twice.
287 You may also embed any kind of lisp expression. If they return a string, it
290 Do [M-x vm-serial-expand-tokens] in order to see how things change ...
292 Example of a embedded lisp expression:
294 the current date is $$(format-time-string \"%D %r\").
296 $$(center-line) Center this line
300 The following tokens are currently defined:
302 Token Documentation (the example follows in the next line)
304 (function (lambda (tk)
305 (concat (car tk) \"\\t\" (caddr tk) \"\n\t$\" (car tk))))
306 vm-serial-token-alist \"\n\")
309 If you thing there are other tokens which should be added to this list, please
312 mailto:Robert Fenk"))
313 "*Alist of default mail templates.
314 Set this by calling `vm-serial-set-mail'!
317 ((SYMBOLIC-NAME CONDITION MAIL-FORM)
320 When calling `vm-serial-yank-mail' interactively one will be prompted for
321 a SYMBOLIC-NAME of a mail from. If called non interactively it will
322 search for the first condition which evaluates to true and inserts the
323 corresponding mail. If CONDITION is a string it is matched against the
324 To-header otherwise it is evaluated."
326 :type '(repeat (list (string :tag "Name")
327 (choice :tag "Condition"
328 (const :tag "NEVER" nil)
329 (const :tag "ALWAYS" t)
330 (string :tag "Regexp" "emailaddress")
331 (variable-item :tag "Relpy" vm-reply-list)
332 (variable-item :tag "Forward" vm-forward-list)
333 (variable-item :tag "Redistribute" vm-redistribute-list)
335 (string :tag "Message-Template"))))
337 (defcustom vm-serial-cookie "$"
338 "*The string which begins a token or Lisp expression.
339 See `vm-serial-expand-tokens' for information about valid tokens."
343 (defcustom vm-serial-fcc nil
344 "*Whether to keep a FCC from the source mail within each serial mail.
345 If the function `vm-postpone-message' (from vm-pine) is present it will
346 also save the source message in the specified folder otherwise there is
347 no way to save the source message."
351 (defcustom vm-serial-mail-signature nil
352 "*Text inserted at the `sig'-token of a mail buffer.
353 The semantics are equal to those of variable `mail-signature', however you
354 should disable variable `mail-signature', since it interacts badly with
355 vm-serial, i.e. set vm-serial-mail-signature to the value of variable
356 `mail-signature' and set variable `mail-signature' to nil!"
358 :type '(choice (const :tag "None" nil)
359 (const :tag "The content of `mail-signature-file'" t)
360 (function-item :tag "Function")
361 (sexp :tag "Lisp-Form")))
363 (defvar vm-serial-to nil
364 "The recipient of the currently expanded message.")
366 (defvar vm-serial-body-contents nil
367 "The message body of the currently replied or forwarded message.")
369 (defcustom vm-serial-unknown-to "unknown"
370 "*The string displayed for recipients without a real name.
371 If set to something different than a string it will be evaluated in order to
376 (defvar vm-serial-source-buffer
378 "The source buffer of the currently expanded template.
379 When doing a `vm-serial-send-mail' this will point to the source
380 buffer containing the original message.")
382 (defvar vm-serial-send-mail-buffer "*vm-serial-mail*"
383 "*Name of the buffer use by `vm-serial-send-mail' for expanded template.")
385 (defvar vm-serial-send-mail-jobs
387 "Remaining list of addresses which have to be processed after editing.")
389 (make-variable-buffer-local 'vm-serial-source-buffer)
390 (make-variable-buffer-local 'vm-serial-send-mail-jobs)
392 ;;-----------------------------------------------------------------------------
393 (defun vm-serial-get-completing-list (alist)
394 "Return cars from ALIST for completion."
395 (mapcar (lambda (e) (list (car e))) alist))
397 ;;-----------------------------------------------------------------------------
398 (defvar vm-serial-token-history nil)
400 (defun vm-serial-set-token (&optional token newvalue doc)
401 "Set vm-serial TOKEN to NEWVALUE with DOC.
402 You may remove a token by specifying just the TOKEN as argument."
404 (let* ((token (completing-read "Token: "
405 (vm-serial-get-completing-list
406 vm-serial-token-alist)
408 vm-serial-token-history))
409 (value (read-expression
411 (format "%S" (cdr (assoc var vm-serial-token-alist))))))
413 (let ((tk (assoc token vm-serial-token-alist)))
416 (setcdr tk (list newvalue doc))
417 (setq vm-serial-token-alist (delete tk vm-serial-token-alist)))
418 (setq vm-serial-token-alist
419 (nconc vm-serial-token-alist
420 (list (list token newvalue doc)))))))
422 (defun vm-serial-set-tokens (token-list)
423 "Set `vm-serial-token-alist' according to TOKEN-LIST.
424 Is a list of (TOKEN NEWVALUE DOC) elements"
427 (setq token-value (car token-list))
428 (vm-serial-set-token (car token-value) (cadr token-value)
430 (setq token-list (cdr token-list)))))
432 (defun vm-serial-get-token (&optional token)
433 "Return value of vm-serial TOKEN."
434 (interactive (list (completing-read "Token: "
435 (vm-serial-get-completing-list
436 vm-serial-token-alist)
438 vm-serial-token-history)))
439 (let ((value (assoc token vm-serial-token-alist)))
442 (warn "There is no vm-serial token `%s'" token)
445 (defun vm-serial-eval-token-value (&optional token-value)
446 "Return string value by evaluation TOKEN-VALUE."
447 (if (stringp token-value)
450 (cond ((and (listp token-value) (stringp (car token-value)))
451 (setq token-value (vm-serial-random-string token-value)))
452 ((functionp token-value)
453 (setq token-value (funcall token-value)))
455 (setq token-value (eval token-value))))
456 (error (setq token-value nil)
457 (warn (format "Token `%s' caused a %S"
462 ;;-----------------------------------------------------------------------------
463 (defun vm-serial-get-emails (&optional header)
464 "Return the recipient of current message.
465 Optional argument HEADER is the header to get the recipients from."
466 (setq header (or header "To:"))
467 (let ((to (vm-mail-mode-get-header-contents header)))
468 (if (functionp 'bbdb-extract-address-components)
469 (car (bbdb-extract-address-components to))
470 (mail-extract-address-components to))))
472 (defun vm-serial-get-to ()
473 "Return the recipient of current message."
475 (vm-serial-get-emails "To:")))
477 (defun vm-serial-get-name (&optional part name)
479 (and vm-serial-to (car vm-serial-to))
480 (let ((to (vm-serial-get-to)))
483 (eval vm-serial-unknown-to)))
484 (part (cond ((stringp part) part)
485 ((equal part 'first) "^\\(\\w+\\)[\t ._]")
486 ((equal part 'last) "^\\w+[\t ._]+\\(.+\\)$"))))
488 (if (and part (string-match part name))
489 (match-string 1 name)
492 (defun vm-serial-get-bbdb-name (&optional part name)
493 (let* ((to (vm-serial-get-to))
494 (rec (bbdb-search-simple nil (cadr to))))
496 (cond ((equal part 'first) (or (bbdb/sc-consult-attr (cadr to))
497 (bbdb-record-firstname rec)))
498 ((equal part 'last) (bbdb-record-lastname rec)))
499 (vm-serial-get-name part name))))
501 ;;-----------------------------------------------------------------------------
502 (defun vm-serial-set-mails (mail-alist)
503 "Set `vm-serial-mails-alist' according to MAIL-ALIST."
505 (setq mail-alist (reverse mail-alist))
507 (setq m (assoc (caar mail-alist) vm-serial-mails-alist))
509 (setq vm-serial-mails-alist (delete m vm-serial-mails-alist)))
510 (add-to-list 'vm-serial-mails-alist (car mail-alist))
511 (setq mail-alist (cdr mail-alist)))))
513 (defun vm-serial-get-mail (&optional mail)
514 "Return the mail body associated with MAIL."
515 (let ((value (assoc mail vm-serial-mails-alist)))
516 (if value (car (last value)) nil)))
518 (defvar vm-serial-mail-history nil
519 "History for `vm-serial-yank-mail'.")
521 (defun vm-serial-find-default-mail ()
522 "Return the first recipient."
523 (let ((to (vm-decode-mime-encoded-words-in-string
524 (or (vm-mail-mode-get-header-contents "To:")
525 (vm-mail-mode-get-header-contents "CC:")
526 (vm-mail-mode-get-header-contents "BCC:")
528 (mails-alist vm-serial-mails-alist)
531 (if (string-match "^\\s-*\\(.*[^ \t]\\)\\s-*$" to)
532 (setq to (match-string 1 to)))
534 (setq m (car mails-alist))
535 (if (and (> (length m) 2)
536 (cond ((stringp (cadr m))
537 (let ((case-fold-search t))
538 (string-match (cadr m) to)))
539 ((functionp (cadr m))
546 (setq mails-alist (cdr mails-alist)))
549 (defun vm-serial-auto-yank-mail (&optional mail no-expand)
550 "Yank the mail associated with MAIL.
551 If MAIL is nil search for a default mail, i.e. the first which evaluates its
552 condition to true. When called with a prefix argument or if NO-EXPAND is non
553 nil no tokens will be expanded after yanking.
555 This is like `vm-serial-yank-mail', but it ensures to yank only if the buffer
556 is no serial mail buffer and if there was no yank-mail before!"
557 (if (and (not vm-serial-source-buffer)
558 (not vm-redistribute-list)
559 (not (local-variable-p 'vm-serial-body-contents (current-buffer)))
560 (boundp 'vm-postponed-message-folder-buffer)
561 (not vm-postponed-message-folder-buffer))
562 (vm-serial-yank-mail (or mail (vm-serial-find-default-mail))
565 (defvar vm-serial-yank-mail-choice nil)
566 (make-variable-buffer-local 'vm-serial-yank-mail-choice)
568 (defun vm-serial-yank-mail (&optional mail no-expand)
569 "Yank the template associated with MAIL.
571 If MAIL is nil search for a default template, i.e. the first one which
572 evaluates its condition to true. When called with a prefix argument ask for
573 a template and with another prefix argument or if NO-EXPAND is non nil
574 no tokens will be expanded after yanking.
576 You may bind this to [C-c C-t] in mail-mode in order to automatically yank
577 the right mail into the composition buffer and move the cursor to the
580 I try to be clever when to delete the existing buffer contents and when to
581 expand the tokens, however if this does not satisfy you please report it to
589 (setq no-expand (if (= mail 16) '(t))
590 mail (completing-read
592 (vm-serial-get-completing-list
593 vm-serial-mails-alist)
596 (cons (vm-serial-find-default-mail)
598 vm-serial-mail-history)
599 vm-serial-yank-mail-choice mail)))
601 (setq mail (or mail vm-serial-yank-mail-choice (vm-serial-find-default-mail)))
603 (let ((save-point (point)))
605 (message "There is no matching mail form!")
606 (if (local-variable-p 'vm-serial-body-contents (current-buffer))
607 (progn (delete-region (mail-text) (point-max))
608 (setq no-expand (if (and no-expand (listp no-expand))
611 (if (or (interactive-p)
612 (local-variable-p 'vm-serial-body-contents (current-buffer)))
613 (message "Inserting serial mail `%S'." mail)
614 (let ((start (mail-text)) (end (goto-char (point-max))))
615 (make-local-variable 'vm-serial-body-contents)
616 (make-local-variable 'vm-serial-to)
617 (setq vm-serial-to nil
618 vm-serial-body-contents nil)
619 (if (not (or vm-reply-list vm-forward-list))
620 (setq no-expand (if (equal no-expand 'not) nil
621 (if (and no-expand (listp no-expand))
623 (setq vm-serial-body-contents (buffer-substring start end))
624 (delete-region start end))))
626 (let ((value (vm-serial-get-mail mail)))
630 (if (or (and (not vm-forward-list) (not no-expand))
631 (equal no-expand 'not))
632 (vm-serial-expand-tokens)
633 (goto-char save-point)))))
635 ;;-----------------------------------------------------------------------------
636 (defun vm-serial-random-string (string-list)
637 "Randomly return one of the strings in STRING-LIST."
638 (let ((value (nth (mod (random) (length string-list)) string-list)))
639 (cond ((stringp value)
646 (defun vm-serial-expand-tokens (&optional rstart rend)
647 "Expand all tokens within the current mail.
648 This means we search for the `vm-serial-cookie' and if it is followed by a
649 regexp of \"[a-zA-Z][a-zA-Z0-9_-]\" we treat this as a symbol to look up in
650 our `vm-serial-token-alist'. Optionally one may enclose the symbol by curly
651 parenthesis. See the test mail in `vm-serial-mails-alist' for examples.
652 If the cookie is followed by a parenthesis then it is treated as a lisp
653 expression which is evaluated
655 Results evaluating to a string are inserted all other return values are
656 ignored. For non existing tokens or errors during evaluation one will get
660 (let ((token-regexp (concat (regexp-quote vm-serial-cookie)
661 "\\(" (regexp-quote vm-serial-cookie) "\\)*"
663 start end expr result vm-serial-point)
666 (eq (zmacs-region-buffer) (current-buffer)))
667 (setq rstart (goto-char (region-beginning)) rend (region-end))
668 (setq rstart (mail-text) rend (point-max)))
670 (narrow-to-region rstart rend)
671 (while (re-search-forward token-regexp (point-max) t)
673 (setq start (- (match-end 0) 1)
675 (cond ((> (length (match-string 1)) 0)
676 (delete-region (match-beginning 1) (match-end 1)))
678 (setq end (scan-sexps start 1))
680 (setq expr (read (current-buffer)))
681 (delete-region (- start 1) end)
682 (setq result (vm-serial-eval-token-value expr)))
683 ((looking-at "\\({\\)?\\([a-zA-Z][a-zA-Z0-9_-]*\\)\\(}\\)?")
684 (setq start (match-beginning 2))
685 (setq end (match-end 2))
686 (setq expr (buffer-substring start end))
687 (if (and (not (and (match-end 1) (match-end 3)))
688 (or (match-end 1) (match-end 3)))
689 (error "Invalid token expression `%s'"
691 (delete-region (- (match-beginning 0) 1) (match-end 0))
692 (setq result (vm-serial-eval-token-value
693 (vm-serial-get-token expr))))
695 (if (and result (stringp result))
696 (insert (format "%s" result))))
699 (goto-char vm-serial-point))))
701 (defvar vm-serial-insert-token-history nil)
703 (defun vm-serial-insert-token (token)
704 "Reads a valid token, inserts it at point and expands it."
708 (if vm-serial-insert-token-history
709 (concat " (default: "
710 (car vm-serial-insert-token-history)
713 (mapcar (lambda (tok) (list (car tok)))
714 vm-serial-token-alist)
718 'vm-serial-insert-token-history)))
719 (setq vm-serial-insert-token-history
720 (delete "" vm-serial-insert-token-history))
721 (if (string= "" token)
722 (setq token (car vm-serial-insert-token-history)))
724 (error "Error: you have to enter a toke name!"))
725 (let ((start (point)))
726 (insert vm-serial-cookie token)
727 (vm-serial-expand-tokens start (point))))
729 ;;-----------------------------------------------------------------------------
730 (defvar vm-serial-sent-cnt nil)
731 (defvar vm-serial-edited-cnt nil)
732 (defvar vm-serial-killed-cnt nil)
733 (defvar vm-serial-send-mail-exit nil)
735 (defun vm-serial-send-mail-increment (variable)
737 (set-buffer vm-serial-source-buffer)
738 (eval (list 'vm-increment variable))))
741 (defun vm-serial-send-mail-and-exit (&optional non-interactive)
742 "Like `vm-serial-send-mail' but kills the buffer after sending all."
744 (make-local-variable 'vm-serial-send-mail-exit)
745 (setq vm-serial-send-mail-exit t)
746 (vm-serial-send-mail non-interactive))
748 (defun vm-serial-send-mail (&optional non-interactive done)
749 "Send an expanded mail to each recipient listed in the To-header.
750 This will create a new buffer for expanding the tokens and user interaction.
751 You may send each mail interactively, that means you may send the message as
752 it is, or you may edit it before sending or you may skip it.
754 If called with a prefix argument or NON-INTERACTIVE set to non nil, no
755 questions will bother you!"
758 (remove-hook 'kill-buffer-hook 'vm-serial-send-mail t)
760 (if vm-serial-source-buffer
761 (progn (set-buffer vm-serial-source-buffer)
764 (if (get-buffer vm-serial-send-mail-buffer)
766 (kill-buffer (get-buffer vm-serial-send-mail-buffer))))
770 (let ((vm-frame-per-composition nil))
771 (flet ((vm-display (buffer display commands configs
772 &optional do-not-raise)
774 (vm-mail-internal vm-serial-send-mail-buffer))
775 (get-buffer vm-serial-send-mail-buffer))))
776 (source-buffer (current-buffer))
779 (if (and (not vm-serial-send-mail-jobs) (not done))
780 (if (not (setq to (mail-fetch-field "To" nil t)))
781 (error "There are no recipients in %s!" (buffer-name))
782 (setq vm-serial-send-mail-jobs
783 (if (functionp 'bbdb-extract-address-components)
784 (bbdb-extract-address-components to)
785 (mapcar 'mail-extract-address-components
786 (bbdb-split to ","))))
787 (make-local-variable 'vm-serial-sent-cnt)
788 (make-local-variable 'vm-serial-edited-cnt)
789 (make-local-variable 'vm-serial-killed-cnt)
790 (setq vm-serial-sent-cnt 0
791 vm-serial-edited-cnt 0
792 vm-serial-killed-cnt 0)))
794 ;; mail-extract-address-components isn't good at all! Fix it!
796 (set-buffer work-buffer)
797 (setq major-mode 'mail-mode))
799 (while (and (not work) vm-serial-send-mail-jobs)
800 (setq to (car vm-serial-send-mail-jobs)
801 to-string (if (car to)
802 (concat (car to) " <" (cadr to) ">")
804 (copy-to-buffer work-buffer (point-min) (point-max))
806 (set-buffer work-buffer)
807 (goto-char (point-min))
808 (vm-mail-mode-remove-header "To:")
809 (mail-position-on-field "To")
811 (if (not vm-serial-fcc)
812 (vm-mail-mode-remove-header "FCC:"))
813 (setq vm-serial-to to
814 vm-serial-source-buffer source-buffer)
815 (setq buffer-undo-list t)
816 (vm-serial-expand-tokens)
818 (if (not non-interactive)
820 (switch-to-buffer work-buffer)
822 (message "(q)uit session or (e)dit, (s)end or (k)ill this mail to `%s'?"
824 (setq command (read-char-exclusive))
825 (cond ((= command ?e)
826 (vm-serial-send-mail-increment 'vm-serial-edited-cnt)
829 (vm-serial-send-mail-increment 'vm-serial-sent-cnt)
832 (vm-serial-send-mail-increment 'vm-serial-killed-cnt))
835 (t (message "Invalid command!")
837 (setq command nil)))))
839 (vm-serial-send-mail-increment 'vm-serial-sent-cnt)))
841 (setq vm-serial-send-mail-jobs (cdr vm-serial-send-mail-jobs)))
843 ;; ok there was an exit or the like
844 (if (equal work 'edit)
845 (progn ;; and we want to edit the outgoing mail before sending
846 (switch-to-buffer work-buffer)
847 (run-hooks 'vm-mail-hook)
848 (run-hooks 'vm-mail-mode-hook)
849 (setq buffer-undo-list nil)
850 (make-local-hook 'kill-buffer-hook)
851 (add-hook 'kill-buffer-hook
853 (vm-serial-send-mail-increment 'vm-serial-killed-cnt))
855 (add-hook 'kill-buffer-hook 'vm-serial-send-mail t t)
856 (make-local-hook 'mail-send-hook)
857 (add-hook 'mail-send-hook
859 (vm-serial-send-mail-increment 'vm-serial-sent-cnt))
861 (remove-hook 'kill-buffer-hook 'vm-save-killed-message-hook t)
862 (message "Kill or send this mail to get to the next mail!"))
864 ;; get rid of the work buffer and go back to the source
865 (kill-buffer work-buffer)
866 (switch-to-buffer source-buffer)
868 (if (not (equal work 'quit))
869 (let ((fcc (vm-mail-mode-get-header-contents "FCC:")))
871 (message "%s mail%s sent, %s edited and %s killed by vm-serial!"
872 (if (= vm-serial-sent-cnt 1) "One" vm-serial-sent-cnt)
873 (if (= vm-serial-sent-cnt 1) "" "s")
874 vm-serial-edited-cnt vm-serial-killed-cnt)
876 ;; this was the last mail so is there some FCC work to do?
877 (if (and fcc (not vm-serial-send-mail-jobs))
878 (if (not (functionp 'vm-postpone-message))
879 (error "vm-pine.el is needed to save source messages!")
880 ;; no postponed header for this!!
881 (vm-mail-mode-remove-header "FCC:")
882 (vm-postpone-message fcc vm-serial-send-mail-exit t))
883 (if vm-serial-send-mail-exit
884 (kill-this-buffer))))))))
886 (defadvice vm-mail-send-and-exit (after vm-serial-send-mail activate)
887 (if vm-serial-source-buffer
890 ;;-----------------------------------------------------------------------------
893 ;;; vm-serial.el ends here