home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / misc / feedmail.el < prev    next >
Encoding:
Text File  |  1991-06-06  |  19.5 KB  |  497 lines

  1. ;;; From wjc@hos1cad.ATT.COM Fri May 24 13:28:58 1991
  2. ;;; Xref: cbnewsh gnu.emacs.sources:326 comp.emacs:6477
  3. ;;; Newsgroups: gnu.emacs.sources,comp.emacs
  4. ;;; Path: cbnewsh!hoswjc!wjc
  5. ;;; From: wjc@hos1cad.ATT.COM (Bill Carpenter)
  6. ;;; Subject: feedmail.el, patchlevel 2
  7. ;;; Organization: AT&T Bell Laboratories
  8. ;;; Date: Fri, 24 May 1991 17:23:50 GMT
  9. ;;; Message-ID: <1991May24.172350.25288@cbnewsh.att.com>
  10. ;;; References: <1991Mar31.011705.9965@cbnewsh.att.com>
  11. ;;;     <1991Apr5.135249.3249@cbnewsh.att.com>
  12. ;;; Sender: bill@cbnewsh.att.com (william.j.carpenter)
  13. ;;; Lines: 481
  14. ;;; 
  15. ;;; Here's the latest version of feedmail.el, a replacement for parts of
  16. ;;; GNUemacs' sendmail.el (specifically, it's what handles your outgoing
  17. ;;; mail after you type C-c C-c in mail mode).
  18. ;;; 
  19. ;;; This version contains the following new things:
  20. ;;; 
  21. ;;;    * fix for handling default-case-fold-search
  22. ;;;    * involve user-full-name in default from line
  23. ;;;    * fix for my improper use of mail-strip-quoted-names when
  24. ;;;      addresses contain a mix of "<>" and "()" styles
  25. ;;;    * new feature allowing optional generation of Message-ID
  26. ;;; 
  27. ;;; feedmail.el
  28. ;;; LCD record:
  29. ;;; feedmail|Bill Carpenter|william.j.carpenter@att.com|Outbound mail handling|91-05-24|2|feedmail.el
  30. ;;;
  31. ;;; Written by Bill Carpenter <william.j.carpenter@att.com>
  32. ;;; original,      31 March 1991
  33. ;;; patchlevel 1,   5 April 1991
  34. ;;; patchlevel 2,  24 May   1991
  35. ;;;
  36. ;;; As far as I'm concerned, anyone can do anything they want with
  37. ;;; this specific piece of code.  No warranty or promise of support is
  38. ;;; offered.
  39. ;;;
  40. ;;; This stuff does in elisp the stuff that used to be done
  41. ;;; by the separate program "fakemail" for processing outbound email.
  42. ;;; In other words, it takes over after you hit "C-c C-c" in mail mode.
  43. ;;; By appropriate setting of options, you can still use "fakemail",
  44. ;;; or you can even revert to sendmail (which is not too popular
  45. ;;; locally).  See the variables at the top of the elisp for how to
  46. ;;; achieve these effects:
  47. ;;;
  48. ;;;    --- you can get one last look at the prepped outbound message and
  49. ;;;        be prompted for confirmation
  50. ;;;
  51. ;;;    --- removes BCC: headers after getting address info
  52. ;;;
  53. ;;;    --- does smart filling of TO: and CC: headers
  54. ;;;
  55. ;;;    --- processes FCC: lines and removes them
  56. ;;;
  57. ;;;    --- empty headers are removed
  58. ;;;
  59. ;;;    --- can force FROM: or SENDER: line
  60. ;;;
  61. ;;;    --- can generate a Message-ID line
  62. ;;;
  63. ;;;    --- strips comments from address info (both "()" and "<>" are
  64. ;;;        handled via a call to mail-strip-quoted-names); the
  65. ;;;        comments are stripped in the simplified address list given
  66. ;;;        to a subprocess, not in the headers in the mail itself
  67. ;;;        (they are left unchanged, modulo smart filling)
  68. ;;;
  69. ;;;    --- error info is pumped into a normal buffer instead of the
  70. ;;;        minibuffer
  71. ;;;
  72. ;;;    --- just before the optional prompt for confirmation, lets you
  73. ;;;        run a hook on the prepped message and simplified address
  74. ;;;        list
  75. ;;;
  76. ;;;    --- you can specify something other than /bin/mail for the
  77. ;;;        subprocess
  78. ;;;
  79. ;;; After a few options below, you will find the function
  80. ;;; feedmail-send-it.  Everything after that function is just local
  81. ;;; stuff for this file.  There are two ways you can use the stuff in
  82. ;;; this file:
  83. ;;;
  84. ;;; (1)  Put the contents of this file into sendmail.el and change the
  85. ;;; name of feedmail-send-it to sendmail-send-it, replacing that
  86. ;;; function in sendmail.el.
  87. ;;;
  88. ;;;                              or
  89. ;;;
  90. ;;; (2)  Save this file as feedmail.el somewhere on your elisp
  91. ;;; loadpath; byte-compile it.  Put the following lines somewhere in
  92. ;;; your ~/.emacs stuff:
  93. ;;;
  94. ;;;        (setq send-mail-function 'feedmail-send-it)
  95. ;;;        (autoload 'feedmail-send-it "feedmail")
  96. ;;;
  97.  
  98.  
  99. (defvar feedmail-confirm-outgoing nil
  100.   "*If non-nil, gives a y-or-n confirmation prompt after prepping,
  101. before sending mail.")
  102.  
  103.  
  104. (defvar feedmail-nuke-bcc t
  105.   "*Non-nil means get rid of the BCC: lines from the message header
  106. text before sending the mail.  In any case, the BCC: lines do
  107. participate in the composed address list.  You probably want to keep
  108. them if you're using sendmail (see feedmail-buffer-eating-function).")
  109.  
  110.  
  111. (defvar feedmail-fill-to-cc t
  112.   "*Non-nil means do smart filling (line-wrapping) of TO: and CC: header
  113. lines.  If nil, the lines are left as-is.  The filling is done after
  114. mail address alias expansion.")
  115.  
  116.  
  117. (defvar feedmail-fill-to-cc-fill-column default-fill-column
  118.   "*Fill column used when wrapping mail TO: and CC: lines.")
  119.  
  120.  
  121. (defvar feedmail-nuke-empty-headers t
  122.   "*If non-nil, headers with no contents are removed from the outgoing
  123. email.  A completely empty SUBJECT: header is always removed,
  124. regardless of the setting of this variable.  The only time you would
  125. want them left in would be if you used some headers whose presence
  126. indicated something rather than their contents.")
  127.  
  128. ;;; wjc sez:  I think the use of the SENDER: line is pretty pointless,
  129. ;;; but I left it in to be compatible with sendmail.el and because
  130. ;;; maybe some distant mail system needs it.  Really, though, if you
  131. ;;; want a sender line in your mail, just put one in there and don't
  132. ;;; wait for feedmail to do it for you.
  133.  
  134. (defvar feedmail-sender-line nil
  135.   "*If nil, no SENDER: header is forced.  If non-nil and the email
  136. already has a FROM: header, a SENDER: header is forced with this as
  137. its contents.  You can probably leave this nil, but if you feel like
  138. using it, a good value would be a fully-qualified domain name form of
  139. your address.  For example, william.j.carpenter@att.com.  Don't
  140. include a trailing newline or the keyword SENDER:.  They're
  141. automatically provided.")
  142.  
  143.  
  144. ;; user-full-name suggested by kpc@ptolemy.arc.nasa.gov (=Kimball Collins)
  145. (defvar feedmail-from-line
  146.   (concat (user-login-name) "@" (system-name) " (" (user-full-name) ")")
  147.   "*If non-nil and the email has no FROM: header, one will be forced
  148. with this as its contents. A good value would be a fully-qualified
  149. domain name form of your address.  For example, william.j.carpenter@att.com.
  150. (The default value of this variable is probably not very good, since
  151. it doesn't have a domain part.)  Don't include a trailing newline or
  152. the keyword FROM:.  They're automatically provided.")
  153.  
  154.  
  155. ;;; Here's how I use the GNUS Message-ID generator for mail but not
  156. ;;; for news postings:
  157. ;;;
  158. ;;;   (setq feedmail-message-id-generator 'wjc:gnusish-message-id)
  159. ;;;   (setq gnus-your-domain "hos1cad.ATT.COM")
  160. ;;;   
  161. ;;;   (defun wjc:gnusish-message-id ()
  162. ;;;     (require 'gnuspost)
  163. ;;;     (if (fboundp 'wjc:gnus-inews-message-id)
  164. ;;;         (wjc:gnus-inews-message-id)
  165. ;;;       (gnus-inews-message-id)))
  166. ;;;   
  167. ;;;   (setq news-inews-hook
  168. ;;;         '(lambda () 
  169. ;;;            (defun gnus-inews-date () nil)
  170. ;;;            (fset 'wjc:gnus-inews-message-id (symbol-function 'gnus-inews-message-id))
  171. ;;;            (defun gnus-inews-message-id () nil)
  172. ;;;            ))
  173. ;;;   
  174. (defvar feedmail-message-id-generator nil
  175.   "*If non-nil, should be a function (called with no arguments) which
  176. will generate a unique message ID which will be inserted on a
  177. Message-ID: header.  The message ID should be the return value of the
  178. function.  Don't include trailing newline, leading space, or the
  179. keyword MESSAGE-ID.  They're automatically provided.  Do include
  180. surrounding <> brackets.  For an example of a message ID generating
  181. function, you could look at the GNUS function gnus-inews-message-id.
  182. When called, the current buffer is the prepped outgoing mail buffer
  183. (the function may inspect it, but shouldn't modify it).  If the returned
  184. value doesn't contain any non-whitespace characters, no message ID
  185. header is generated, so you could generate them conditionally,
  186. based on the contents of the mail.")
  187.  
  188.  
  189. (defun feedmail-confirm-addresses-hook-example ()
  190.   "An example of a last chance hook that shows the simple addresses
  191. and gets a confirmation.  Use as (setq feedmail-last-chance-hook
  192. 'feedmail-confirm-addresses-hook-example)."
  193.   (save-window-excursion 
  194.     (display-buffer feedmail-address-buffer)
  195.     (if (not (y-or-n-p "How do you like them apples? "))
  196.         (error "Sending...gave up in last chance hook"))))
  197.  
  198.  
  199. (defvar feedmail-last-chance-hook nil
  200.   "*User's last opportunity to modify the message on its way out.  It
  201. has already had all the header prepping from the standard package.
  202. The next step after running the hook will be to push the buffer into a
  203. subprocess that mails the mail.  The hook might be interested in these
  204. buffers:  (1) feedmail-prepped-text-buffer contains the header and body
  205. of the message, ready to go;  (2) feedmail-address-buffer contains the
  206. space-separated, simplified list of addresses which is to be given to
  207. the subprocess (the hook may change them).  feedmail-error-buffer is
  208. an empty buffer intended to soak up errors for display to the user.
  209. If the hook allows interactive activity, the user should not send more
  210. mail while in the hook since some of the internal buffers will be reused.")
  211.  
  212.  
  213. (defvar feedmail-buffer-eating-function 'feedmail-buffer-to-binmail
  214.   "*Function used to send the prepped buffer to a subprocess.  The
  215. function's three (mandatory) arguments are: (1) the buffer containing
  216. the prepped message; (2) a buffer where errors should be directed; and
  217. (3) a string containing the space-separated list of simplified
  218. addresses.  Two popular choices for this are 'feedmail-buffer-to-binmail
  219. and 'feedmail-buffer-to-sendmail.  If you use the sendmail form, you
  220. probably want to set feedmail-nuke-bcc to nil.  If you use the binmail
  221. form, check the value of feedmail-binmail-template.")
  222.  
  223.  
  224. (defvar feedmail-binmail-template (if mail-interactive "/bin/mail %s" "/bin/rmail %s")
  225.   "*Command template for the subprocess which will get rid of the
  226. mail.  It can result in any command understandable by /bin/sh.  The
  227. single '%s', if present, gets replaced by the space-separated,
  228. simplified list of addressees.  Used in feedmail-buffer-to-binmail to
  229. form the shell command which will receive the contents of the prepped
  230. buffer as stdin.  If you'd like your errors to come back as mail
  231. instead of immediately in a buffer, try /bin/rmail instead of
  232. /bin/mail (this can be accomplished by keeping the default nil setting
  233. of mail-interactive).  You might also like to consult local mail
  234. experts for any other interesting command line possibilities.")
  235.  
  236.  
  237. ;; feedmail-buffer-to-binmail and feedmail-buffer-to-sendmail are the
  238. ;; only things provided for values for the variable
  239. ;; feedmail-buffer-eating-function.  It's pretty easy to write your
  240. ;; own, though.
  241.  
  242. (defun feedmail-buffer-to-binmail (prepped-mail-buffer mail-error-buffer simple-address-list)
  243.   "Function which actually calls /bin/mail as a subprocess and feeds the buffer to it."
  244.   (save-excursion
  245.     (set-buffer prepped-mail-buffer)
  246.     (apply 'call-process-region
  247.            (append (list (point-min) (point-max)
  248.                          "/bin/sh" nil mail-error-buffer nil "-c"
  249.                          (format feedmail-binmail-template simple-address-list ))))
  250.     ) ;; save-excursion
  251.   )
  252.  
  253.  
  254. (defun feedmail-buffer-to-sendmail (prepped-mail-buffer feedmail-error-buffer simple-address-list)
  255.   "Function which actually calls sendmail as a subprocess and feeds the buffer to it."
  256.   (save-excursion
  257.     (set-buffer prepped-mail-buffer)
  258.     (apply 'call-process-region
  259.            (append (list (point-min) (point-max)
  260.                        (if (boundp 'sendmail-program)
  261.                            sendmail-program
  262.                          "/usr/lib/sendmail")
  263.                        nil feedmail-error-buffer nil
  264.                        "-oi" "-t")
  265.                  ;; Don't say "from root" if running under su.
  266.                  (and (equal (user-real-login-name) "root")
  267.                       (list "-f" (user-login-name)))
  268.                  ;; These mean "report errors by mail"
  269.                  ;; and "deliver in background".
  270.                  (if (null mail-interactive) '("-oem" "-odb"))))
  271. ))
  272.  
  273.  
  274. ;; feedmail-send-it is the only "public" function is this file.
  275. ;; All of the others are just little helpers.
  276. (defun feedmail-send-it ()
  277.   (let* ((default-case-fold-search t)
  278.          (feedmail-error-buffer (get-buffer-create " *Outgoing Email Errors*"))
  279.          (feedmail-prepped-text-buffer (get-buffer-create " *Outgoing Email Text*"))
  280.          (feedmail-address-buffer (get-buffer-create " *Outgoing Email Address List*"))
  281.          (feedmail-raw-text-buffer (current-buffer))
  282.          (case-fold-search nil)
  283.          end-of-headers-marker)
  284.  
  285.     (unwind-protect (save-excursion
  286.         (set-buffer feedmail-prepped-text-buffer) (erase-buffer)
  287.  
  288.         ;; jam contents of user-supplied mail buffer into our scratch buffer
  289.         (insert-buffer-substring feedmail-raw-text-buffer)
  290.  
  291.         ;; require one newline at the end.
  292.         (goto-char (point-max))
  293.         (or (= (preceding-char) ?\n) (insert ?\n))
  294.  
  295.         ;; Change header-delimiter to be what mailers expect (empty line).
  296.         (goto-char (point-min))
  297.         (re-search-forward (concat "^" (regexp-quote mail-header-separator) "\n"))
  298.         (replace-match "\n")
  299.         ;; why was this backward-char here?
  300.         ;;(backward-char 1)
  301.         (setq end-of-headers-marker (point-marker))
  302.  
  303.         (if mail-aliases (expand-mail-aliases (point-min) end-of-headers-marker))
  304.  
  305.         ;; make it pretty
  306.         (if feedmail-fill-to-cc (feedmail-fill-to-cc-function end-of-headers-marker))
  307.         ;; ignore any blank lines in the header
  308.         (goto-char (point-min))
  309.         (while (and (re-search-forward "\n\n\n*" end-of-headers-marker t) (< (point) end-of-headers-marker))
  310.           (replace-match "\n"))
  311.       
  312.         (let ((case-fold-search t))
  313.           (feedmail-deduce-address-list feedmail-prepped-text-buffer (point-min) end-of-headers-marker)
  314.           (save-excursion (set-buffer feedmail-address-buffer)
  315.                           (goto-char (point-min))
  316.                           (if (not (re-search-forward "\\S-" (point-max) t))
  317.                               (error "Sending...abandoned, no addressees!")))
  318.  
  319.           ;; Find and handle any BCC fields.
  320.           (if feedmail-nuke-bcc (feedmail-do-bcc end-of-headers-marker))
  321.  
  322.           ;; Find and handle any FCC fields.
  323.           (goto-char (point-min))
  324.           (if (re-search-forward "^FCC:" end-of-headers-marker t)
  325.               (mail-do-fcc end-of-headers-marker))
  326.  
  327.           (goto-char (point-min))
  328.           (if (re-search-forward "^FROM:" end-of-headers-marker t)
  329.               
  330.               ;; If there is a FROM: and no SENDER:, put in a SENDER:
  331.               ;; if requested by user
  332.               (if (and feedmail-sender-line
  333.                        (not (save-excursion (goto-char (point-min))
  334.                            (re-search-forward "^SENDER:" end-of-headers-marker t))))
  335.                   (progn (forward-line 1) (insert "Sender: " feedmail-sender-line "\n")))
  336.  
  337.             ;; no FROM: ... force one?
  338.             (if feedmail-from-line
  339.                 (progn (goto-char (point-min)) (insert "From: " feedmail-from-line "\n")))
  340.             )
  341.  
  342.           ;; don't send out a blank subject line
  343.           (goto-char (point-min))
  344.           (if (re-search-forward "^Subject:[ \t]*\n" end-of-headers-marker t)
  345.               (replace-match ""))
  346.  
  347.           ;; don't send out a blank headers of various sorts
  348.           (goto-char (point-min))
  349.           (and feedmail-nuke-empty-headers  ;; hey, who's an empty-header? 
  350.                (while (re-search-forward "^[A-Za-z0-9-]+:[ \t]*\n" end-of-headers-marker t)
  351.                  (replace-match ""))))
  352.  
  353.         ;; message ID generation
  354.         (if feedmail-message-id-generator
  355.             (progn
  356.               (goto-char (point-min))
  357.               (if (re-search-forward "^MESSAGE-ID:[ \t]*\n" end-of-headers-marker t)
  358.                   (replace-match ""))
  359.               (setq feedmail-msgid-part (funcall feedmail-message-id-generator))
  360.               (goto-char (point-min))
  361.               (and feedmail-msgid-part (string-match "[^ \t]" feedmail-msgid-part)
  362.                   (insert "Message-ID: " feedmail-msgid-part "\n"))))
  363.  
  364.  
  365.         (save-excursion (set-buffer feedmail-error-buffer) (erase-buffer))
  366.  
  367.         (run-hooks 'feedmail-last-chance-hook)
  368.  
  369.         (if (or (not feedmail-confirm-outgoing) (feedmail-one-last-look feedmail-prepped-text-buffer))
  370.             (funcall feedmail-buffer-eating-function feedmail-prepped-text-buffer feedmail-error-buffer
  371.                      (save-excursion (set-buffer feedmail-address-buffer) (buffer-string)))
  372.           (error "Sending...abandoned")
  373.           )
  374.         )  ;; unwind-protect body (save-excursion)
  375.  
  376.       ;; unwind-protect cleanup forms
  377.       (kill-buffer feedmail-prepped-text-buffer)
  378.       (kill-buffer feedmail-address-buffer)
  379.       (set-buffer feedmail-error-buffer)
  380.       (if (zerop (buffer-size))
  381.           (kill-buffer feedmail-error-buffer)
  382.         (progn (display-buffer feedmail-error-buffer)
  383.                (error "Sending...failed")))
  384.       (set-buffer feedmail-raw-text-buffer))
  385.     ) ;; let
  386.   )
  387.  
  388.  
  389. (defun feedmail-do-bcc (header-end)
  390.   "Delete BCC: and their continuation lines from the header area.
  391. There may be multiple BCC: lines, and each may have arbitrarily
  392. many continuation lines."
  393.   (let ((case-fold-search t))
  394.     (save-excursion (goto-char (point-min))
  395.       ;; iterate over all BCC: lines
  396.       (while (re-search-forward "^BCC:" header-end t)
  397.         (delete-region (match-beginning 0) (progn (forward-line 1) (point)))
  398.         ;; get rid of any continuation lines
  399.         (while (and (looking-at "^[ \t].*\n") (< (point) header-end))
  400.           (replace-match ""))
  401.         )
  402.       ) ;; save-excursion
  403.     ) ;; let
  404.   )
  405.  
  406. (defun feedmail-fill-to-cc-function (header-end)
  407.   "Smart filling of TO: and CC: headers.  The filling tries to avoid
  408. splitting lines except at commas.  This avoids, in particular,
  409. splitting within parenthesized comments in addresses."
  410.   (let ((case-fold-search t)
  411.         (fill-prefix "\t")
  412.         (fill-column feedmail-fill-to-cc-fill-column)
  413.         this-line
  414.         this-line-end)
  415.     (save-excursion (goto-char (point-min))
  416.       ;; iterate over all TO:/CC: lines
  417.       (while (re-search-forward "^\\(TO:\\|CC:\\)" header-end t)
  418.         (setq this-line (match-beginning 0))
  419.         (forward-line 1)
  420.         ;; get any continuation lines
  421.         (while (and (looking-at "^[ \t]+") (< (point) header-end))
  422.           (replace-match " ")
  423.           (forward-line 1))
  424.         (setq this-line-end (point-marker))
  425.  
  426.         ;; The general idea is to break only on commas.  Change
  427.         ;; all the blanks to something unprintable; change the
  428.         ;; commas to blanks; fill the region; change it back.
  429.         (subst-char-in-region this-line this-line-end ?   2 t) ;; blank --> C-b
  430.         (subst-char-in-region this-line this-line-end ?, ?  t) ;; comma --> blank
  431.         (fill-region-as-paragraph this-line this-line-end)
  432.  
  433.         (subst-char-in-region this-line this-line-end ?  ?, t) ;; comma <-- blank
  434.         (subst-char-in-region this-line this-line-end  2 ?  t) ;; blank <-- C-b
  435.  
  436.         ;; look out for missing commas before continuation lines
  437.         (save-excursion
  438.           (goto-char this-line)
  439.           (while (re-search-forward "\\([^,]\\)\n\t[ ]*" this-line-end t)
  440.             (replace-match "\\1,\n\t")))
  441.         )
  442.       ) ;; while
  443.     ) ;; save-excursion
  444.   )
  445.  
  446.  
  447. (defun feedmail-deduce-address-list (feedmail-text-buffer header-start header-end)
  448.   "Get address list suitable for command line use on simple /bin/mail."
  449.   (require 'mail-utils)  ;; pick up mail-strip-quoted-names
  450.   (let
  451.       ((case-fold-search t)
  452.        (simple-address-list "")
  453.        this-line
  454.        this-line-end)
  455.     (unwind-protect
  456.         (save-excursion
  457.           (set-buffer feedmail-address-buffer) (erase-buffer)
  458.           (insert-buffer-substring feedmail-text-buffer header-start header-end)
  459.           (goto-char (point-min))
  460.           (while (re-search-forward "^\\(TO:\\|CC:\\|BCC:\\)" header-end t)
  461.             (replace-match "")
  462.             (setq this-line (match-beginning 0))
  463.             (forward-line 1)
  464.             ;; get any continuation lines
  465.             (while (and (looking-at "^[ \t]+") (< (point) header-end))
  466.               (forward-line 1))
  467.             (setq this-line-end (point-marker))
  468.             (setq simple-address-list
  469.                   (concat simple-address-list " "
  470.                           (mail-strip-quoted-names (buffer-substring this-line this-line-end))))
  471.             )
  472.           (erase-buffer)
  473.           (insert-string simple-address-list)
  474.           (subst-char-in-region (point-min) (point-max) 10 ?  t)  ;; newline --> blank
  475.           (subst-char-in-region (point-min) (point-max) ?, ?  t)  ;; comma   --> blank
  476.           (subst-char-in-region (point-min) (point-max)  9 ?  t)  ;; tab     --> blank
  477.  
  478.           (goto-char (point-min))
  479.           ;; tidyness in case hook is not robust when it looks at this
  480.           (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
  481.  
  482.           )
  483.       )
  484.     )
  485.   )
  486.  
  487.  
  488. (defun feedmail-one-last-look (feedmail-prepped-text-buffer)
  489.   "Offer the user one last chance to give it up."
  490.   (save-excursion (save-window-excursion
  491.     (switch-to-buffer feedmail-prepped-text-buffer)
  492.     (y-or-n-p "Send this email? "))))
  493.  
  494.  
  495. (provide 'feedmail)
  496.  
  497.