A core dump of ideas and projects

Using org mode to write email for outlook

· by [Troy Hinckley] · Read in about 8 min · (1598 Words)

I see many threads on Reddit and blog posts about using email inside
Emacs. I mean, I already have org-mode which organizing my whole
digital life. But then all my work email is provided through outlook,
which does not allow me to fetch email with anything other then their
proprietary software.

Microsoft outlooked was designed to be used by people writing
marketing emails, not people talking about code. There is no way to
distinguish what is code from what is text, or call our programmign
symbols from the rest of the prose. Emacs org-mode on the other
hand, was built for working in a techincal environment.

Thankfully org makes it easy to export any set of text html. The hard
part is getting that HTML into outlook. Most of the ideas presented
here were taken this post, and then expanded on.

This is the process I use to write my email with org-mode:

  1. write the email in a org capture buffer
  2. Use a custom function to copy the exported HTML to the clipboard
  3. go to outlook and use a custom VBA function to insert the HTML from
    the clipboard as formatted text

Setting up Emacs

Using an org capture buffer

Using an org capture buffer is perfect for writing email, because I
can save it as a draft if needed, or export the contents and then
throw the buffer away. Also, most of the time the content of intreset
is what I am working on that moment, so I have everything at hand.
Here is the simple template that I use to create emails from the
capture file.

1
2
3
(add-to-list 'org-capture-templates
             '("e" "Email" entry (expand-file-name "email.org" org-directory)
               "* %?" :empty-lines 1))

Exporting from org-mode

Normally if you wanted to export an org header as HTML, you would use
C-c C-e to open the export menu. hH will open a dedicated buffer
with the HTML contents of your org file. from there you can copy the
whole buffer. However I find it much faster to use this helper
function (bound to C-x M-e).

4
5
6
7
8
9
10
11
12
(defun export-org-email ()
  "Export the current org email and copy it to the clipboard"
  (interactive)
  (let ((org-export-show-temporary-export-buffer nil)
        (org-html-head (org-email-html-head)))
    (org-html-export-as-html)
    (with-current-buffer "*Org HTML Export*"
      (kill-new (buffer-string)))
    (message "HTML copied to clipboard")))

Better CSS for export

The default HTML exported by org is spartan to say the least.
Thankfully it is pretty easy to define some custom to CSS that make
things look prettier and play nicer with outlooks HTML rendering
engine. The outlook compatible HTML I use is located here. The
function below adds my CSS to org-html-head. It is called by
export-org-email from the previous section.

13
14
15
16
17
18
19
20
21
22
23
(defun org-email-html-head ()
  "Create the header with CSS for use with email"
  (concat
   "<style type=\"text/css\">\n"
   "<!--/*--><![CDATA[/*><!--*/\n"
   (with-temp-buffer
     (insert-file-contents
      "~/org/org-html-themes/styles/email/css/email.css")
     (buffer-string))
   "/*]]>*/-->\n"
   "</style>\n"))

setting up outlook

Getting the HTML into outlook

This is the tricky part. outlook does not make it easy to insert HTML
inline. I had to learn some VBA and use the outlook code editor. I
hope I never have to do that again.

To add a function to outlook

  1. Press Alt-F11 to bring up the VBA editor.
  2. You should see the default project. Change this project name to
    something more apporopriate. Note that the Project name MUST NOT
    be the name of the function (PrependClipboardHTML) so name it
    something else.
  3. Right click on the project to add a new module and copy in the
    function from below
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Sub PrependClipboardHTML()
    Dim email As Outlook.MailItem
    Dim cBoard As DataObject

    Set email = Application.ActiveInspector.CurrentItem
    Set cBoard = New DataObject

    cBoard.GetFromClipboard
    email.HTMLBody = cBoard.GetText + email.HTMLBody

    Set cBoard = Nothing
    Set email = Nothing
End Sub

Fix object library

This step may not apply to everyone, but in order to get this to work,
I also had to add the Microsoft Forms 2.0 Object Library to the
References. I figured this out by looking at this Reddit thread.

  1. Click on Tools in the menu bar (or use Alt-t).
  2. Select References...
  3. Select Browse...
  4. Browse to C:\Windows\System32\FM20.DLL and select open

Add to Quick Access Toolbar

This function only makes sense in context of an email. To enable it
there, add it to the quick access toolbar at the top.

  1. Press Ctrl-n to open up a new email.
  2. Select the little down arrow at the very top for the Customize Quick Access Toolbar menu.
  3. Select More Commands.
  4. In the drop down for choose commands from: select Macros. You
    should see the PrependClipboardHTML macro you created here.
  5. Add it to the right hand side pane with the Add >> button.
  6. Click on Modify... to change the icon and display name. You can
    also use the arrow to change the ordering in the Quick Access Toolbar

Now clicking on that button will copy clipboard contents into the
email as HTML. Our raw HTML exported from Org mode gets inserted
nicely and we gain the formatting desired.

The other bonus (or maybe the main point) is that now you can also use
a built-in shorcut for the Quick Access Toolbar commands to run this
one. By pressing Alt, you can see a number by your command.

Matching the default font in Outlook

It is nice sometimes to have the default font in outlook match what
you are exporting from org mode. To make this happen, do the following
steps in Outlook.

  1. On the File tab, choose Options > Mail.
  2. Under Compose messages, choose Stationery and Fonts.
  3. On the Personal Stationery tab, under New mail messages or
    Replying or forwarding messages, choose Font.
  4. In the Font box, choose the font, style, size, and color that you
    want to use. You can see a preview of your changes as you make
    them.
  5. Choose OK three times to return to Outlook.

Bonus Content

Here are a few more ideas that are not necessary for this workflow but
are useful to me.

More advanced VBA

The PrependClipboardHTML function I showed above is not actually the
version I use. But I chose to mention present it as the solution
because it is simple and works well. This more advanced version has
two differences

  1. Works with inline email replies
  2. If the subject line is empty, the HTML header at the start of the
    body is used as the subject line. This allows you add the subject
    line in org-mode and have it automatcially inserted.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Sub PrependClipboardHTML()
    Dim email As Outlook.MailItem
    Dim cBoard As DataObject

    Set email = GetCurrentItem()
    Set cBoard = New DataObject

    cBoard.GetFromClipboard

    Dim sText As String
    Dim headerStart As Integer
    Dim headerStartClose As Integer
    Dim HTMLPre As String
    Dim HTMLPost As String
    Dim subject As String
    Dim paragraphStart As Integer

    Dim headerEndStr As String
    Const titleText = "<h1 class=""title"">"

    headerEndStr = "</h1>"
    headerStart = Len(titleText)

    sText = cBoard.GetText
    HTMLPre = sText

    headerStart = InStr(sText, titleText)
    If Not headerStart > 0 Then
        ' if no title text, we use the starting header
        headerStart = InStr(sText, "<h2 id=")
        headerEndStr = "</h2>"
    End If

    ' we use the first header as the subject line if the subject line is empty
    If headerStart > 0 Then
        headerStartClose = InStr(headerStart, sText, ">") + 1
        Dim headerEnd As Integer
        headerEnd = InStr(headerStartClose, sText, headerEndStr)
        If headerEnd > 0 Then
            subject = Mid(sText, _
                headerStartClose, _
                headerEnd - headerStartClose)
            HTMLPre = Mid(sText, 1, headerStart - 1) ' arrays start at 1...
            HTMLPost = Mid(sText, headerEnd + Len(headerEndStr))
        End If
    End If

    email.HTMLBody = HTMLPre + HTMLPost + email.HTMLBody
    ' only use the HTML subject if an email subject is absent
    If Len(email.subject) = 0 Then
        email.subject = subject
    End If

    ' deallocate objects
    Set cBoard = Nothing
    Set email = Nothing

End Sub

' Get either the active email item or the current window
Function GetCurrentItem() As Object
    Dim objApp As Outlook.Application

    Set objApp = Application

    On Error Resume Next
    Select Case TypeName(objApp.ActiveWindow)
        Case "Explorer"
            Set GetCurrentItem = objApp.ActiveExplorer.ActiveInlineResponse
        Case "Inspector"
            Set GetCurrentItem = objApp.activeInspector.CurrentItem
    End Select

    Set objApp = Nothing
End Function

Normalize outlook formatting

Unless you disable it, outlook will try and “prettify” some characters
as you type with non ascii compatible versions. This means that you
will often encounter errors when copying code out of outlook and
trying to paste into a shell or source file. The following function
takes the last paste normalizes it to be ascii compatible.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defun normalize-text (beg end)
  "normalize characters used in Microsoft formatting"
  (let* ((orig-text (buffer-substring beg end))
         (normalized-text
          (thread-last orig-text
            (replace-regexp-in-string "–" "--")
            (replace-regexp-in-string (rx (char "‘’")) "'")
            (replace-regexp-in-string (rx (char "“”")) "\""))))
    (unless (equal orig-text normalized-text)
      (save-excursion
        (goto-char beg)
        (delete-region beg end)
        (insert normalized-text)))))

(defun normalize-region (beg end)
  "normalzie the last paste, or if region is selected, normalize
that region."
  (interactive "r")
  (if (region-active-p)
      (progn (normalize-text beg end)
             (deactivate-mark))
    (apply #'normalize-text (cl-sort (list (point) (mark t)) '<))))

inline source code highlighting

you can include inline code using something like this

1
src_xml[:exports code]{<tag>text</tag>}