I use Emacs for reading and sending email, so I’ve been using emacs-lisp to send mailshots for years (but in a rather clunky way).
The big shortcoming is that it is not hugely convenient getting the data (e.g., student names, email addresses, marks, comments) into emacs-lisp data structures, or conveniently writing the emails.
org-mode makes it all easier.
I present an example here: how to send mails giving feedback on performance in a test.
First we put the data in an org-mode table, giving it a name.
org-mode tables are very ergonomic to enter data directly in. You could also cut and paste a CSV and convert it (insert, highlight, type “C-|”), but it’s likely better to use org-mode to enter the data in the first place.
#+NAME: assignment1 | 1 | Sleepy | 5 | | 2 | Sneezy | 15 | | 3 | Doc | 10 |
Then we write the email (exactly the content of a message-mode Emacs mail-buffer, before sending), but with “%s” marking locations where we insert variable information. This is input as an org-mode EXAMPLE, with a name.
#+NAME: boilerplate #+BEGIN_EXAMPLE To: %s Subject: Assignment 1 BCC: email@example.com --text follows this line-- Dear %s, Homework assignment 1 Your mark on assignment 1 was %s/20. In other words, you %s. Regards, Snow White -- #+END_EXAMPLE
The boilerplate defines fields to fill as:
- full email address
- mark, and
- a verbal summary of the performance.
Note that the boilerplate defines the complete contents of a mail message buffer (apart from the signature, which can be inserted afterwards, as below).
The next block explicitly refers to the student data and the boilerplate (“
:var boilerplate=boilerplate students=assignment1“), and then defines a function,
make-message, which works on each row of the student data table and
- creates a mail buffer, deleting its contents in order to replace them
with our own
- inserts the boilerplate, populating the fields appropriately
- including creating the verbal summary based on the mark
- renames the buffer uniquely (and marks it unmodified, to make it easy
to delete if necessary)
The block then applies the defun to each element of the student-data list (using
mapcar, which is like the
apply family in R).
#+BEGIN_SRC emacs-lisp :results none :var boilerplate=boilerplate students=assignment1 (defun make-message (student) (compose-mail) (delete-region (point-min) (point-max)) (insert (format boilerplate (format "firstname.lastname@example.org" (nth 0 student)) (nth 1 student) (nth 2 student) (cond ((< (nth 2 student) 10) "fluffed it") ((= (nth 2 student) 10) "did okay") ((> (nth 2 student) 10) "aced it")))) (insert "Snow White Housekeeping and Judgementals The Dwarves' House Enchanted Forest") (rename-uniquely) (set-buffer-modified-p nil)) ;; make easy to delete for debug (mapcar (lambda (student) (make-message student)) students) #+END_SRC
The end result is multiple mail buffers, one per student, each containing a mail message ready to send. You can then visit the buffers, and verify before sending. Insert the command
message-send-and-exit at the end of the defun if you are truly reckless!
An example mail buffer:
To: email@example.com Subject: Assignment 1 BCC: firstname.lastname@example.org --text follows this line-- Dear Doc, Homework assignment 1 Your mark on assignment 1 was 10/20. In other words, you did okay. Regards, Snow White -- Snow White Housekeeping and Judgementals The Dwarves' House Enchanted Forest
This is nice because we can enter the data conveniently in an
org-mode table, and similarly enter the email boilerplate in an
EXAMPLE block without worrying too much about escaping quotation marks etc. However, what is really nice is that we can
- do arbitrary things to the text using emacs-lisp (“you fluffed it”)
- process the data tables using, e.g,. R
It is also possible to generate arbitrarily complex HTML and multipart emails, but that’s a topic for another day.