home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 2: Applications / Linux Cubed Series 2 - Applications.iso / editors / emacs / xemacs / xemacs-1.006 / xemacs-1 / lib / xemacs-19.13 / lisp / packages / feedmail.el < prev    next >
Encoding:
Text File  |  1995-01-27  |  19.6 KB  |  502 lines

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