Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send HTML Mail from Cocoa with Mail.app

I'm trying to send html email from Cocoa app, through Mail.app. I want to open new message in Mail.app, include subject, recipient and add HTML Body with links and other content. But can't find the way to do this. I already tried Scripting Bridge, but MailOutgoingMessage class doesn't have content type i can add content in plaintext.

tried AppleScript, something like this:

set htmlContent to read "/Path/index.html"
set recipientList to {"[email protected]"}

tell application "Mail"
    set newMessage to make new outgoing message with properties {subject:"qwerty", visible:true}
    tell newMessage
        make new to recipient at end of to recipients with properties {address:"[email protected]"}
            set html content to htmlContent
        --send
    end tell
end tell

this code send email with html, only if I'm changing --send to send. But i need to send letter later, after user made some changes.

like image 356
rozochkin Avatar asked Jul 10 '13 16:07

rozochkin


People also ask

Can you send HTML email in Apple Mail?

You can choose to send email messages in plain text or rich text (HTML) format.

How do I paste HTML into Apple Mail?

With your HTML email opened in Safari, choose Edit > Select All from the menu bar or press Command – A. This command highlights all of the HTML email on the screen, after which you need to copy the email using Edit > Copy or Command – C. (An HTML email pasted into Apple Mail and ready to send.)

How do you send an email in HTML format?

On the File tab, choose Options > Mail. Under Compose messages, in the Compose messages in this format list, click HTML, Plain Text, or Rich Text.


3 Answers

To recap the problem: Using AppleScript to create a message with HTML content for interactive editing does not work (as of OS X 10.9.2): the new-message form comes up with an empty body.

This should be considered a bug and I encourage everyone to let Apple know at http://bugreport.apple.com - caveat: the html content message class property is not defined in Mail.sdef, Mail.app's AppleScript dictionary, so assigning HTML may not be officially supported.

There is a workaround, but it ain't pretty:

  • Create the message invisibly.
  • Save it as a draft.
  • Open the draft message, at which point the HTML content will appear.

Implementing this robustly is challenging, because several workarounds are required. The following code tries its hardest, though:

Note: Since the code uses GUI scripting, Access for Assistive Devices must be enabled (via System Preferences > Security & Privacy > Accessibility) for the application running this code (e.g., AppleScript Editor or, if run via osascript, Terminal.app).

# Example values; use `read someFile` to read HTML from a file.
set htmlContent to "<html><body><h1>Hello,</h1><p>world.</p></body></html>"
set recipientList to {"[email protected]", "[email protected]"}
set msgSubject to "qwerty"

tell application "Mail"

    # Create the message *invisibly*, and assign subject text
    # as well as the HTML content.
    set newMessage to make new outgoing message with properties ¬
        {visible:false, subject:msgSubject, html content:htmlContent}

    # Add recipients.
    # !! Given the workaround below, this is currently pointless.
    tell newMessage
        repeat with toRcpt in recipientList
            make new to recipient at end of to recipients with properties {address:toRcpt}
        end repeat
    end tell

    # Save the current number of drafts messages.
    set draftCountBefore to count messages of drafts mailbox

    # !! Save the new message as a *draft* - this is necessary
    # for the HTML content to actually appear in the message
    # body when we open the message interactively later.
    save newMessage

    # !! Sadly, it takes a little while for the new message
    # !! to appear in the drafts mailbox, so we must WAIT.
    set newMessageAsDraft to missing value
    repeat with i from 1 to 30 # give up after n * 0.1 secs.
        set draftCountNow to (count messages of drafts mailbox)
        if draftCountNow > draftCountBefore then
            set newMessageAsDraft to message 1 of drafts mailbox
            exit repeat
        end if
        delay 0.1 # sleep a little
    end repeat

    # Abort, if the draft never appeared.
    if newMessageAsDraft is missing value then error "New message failed to appear in the drafts mailbox within the timeout period."

    # Open the new message as a *draft* message - this ensures that 
    # the HTML content is displayed and editable in the message body.
    # !! The ONLY solution I found is to use `redirect`, which, unfortunately,
    # !! *wipes out the recipients*.
    # !! It does, however, ensure that the draft is deleted once the message is sent.
    redirect newMessageAsDraft with opening window

    # Activate Mail.app and thus the draft message's window.
    activate

    # !! Since the recipients have been wiped out, we need to
    # !! add them again - unfortunately, the only way we can do that is to
    # !! *GUI scripting* - simulating invocation of a menu command or
    # !! sending keystrokes.
    tell application "System Events"

        # We must make sure that the target window is active before
        # we can perform GUI scripting on it.
        set newMessageWindow to missing value
        repeat with i from 1 to 30 # give up after n * 0.1 secs.
            tell (first window of (first process whose frontmost is true) whose subrole is not "AXFloatingWindow")
                if name is msgSubject then
                    set newMessageWindow to it
                    exit repeat
                end if
            end tell
            delay 0.1 # sleep a little
        end repeat
        if newMessageWindow is missing value then error "New message failed to become the active window within the timeout period."

        # Turn the list of recipients into comma-delimited *string* for pasting into the To field.
        set {orgTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {","}}
        set recipientListString to (recipientList as text)
        set AppleScript's text item delimiters to orgTIDs

        # Save the current clipboard content.
        set prevClipboardContents to the clipboard

        # Cursor is in the  "To:" field, so use GUI scripting to send the Edit > Paste command now.
        # NOTE: Access for assistive devices must be enabled via System Preferences > Security & Privacy > Accessibility.
        set the clipboard to recipientListString
        my pasteFromClipboard("")

        # Restore the previous clipboard content.
        # !! We mustn't do this instantly, as our temporary content may not have
        # !! finished pasting yet. It would be non-trivial to determine
        # !! when pasting has finished (examining `count of to recipients` doesn't work), 
        # !! so we take our chances with a fixed, small delay.
        delay 0.1
        set the clipboard to prevClipboardContents

        # Place the cursor in the message *body*.
        # !! This works as of Mail.app on OS X 10.9.2, but may break in the future.
        try
            tell newMessageWindow
                tell UI element 1 of scroll area 1
                    set value of attribute "AXFocused" to true
                end tell
            end tell
        end try

    end tell

end tell

(*
 Pastes form the clipboard into the active window of the specified application (process) using GUI scripting
 rather than keyboard shortcuts so as to avoid conflicts with keyboard shortcuts used to invoke this handler.
 Specify "" or `missing value` to paste into the currently active (frontmost) application.
 The target process may be specified by either name or as a process object.

 CAVEAT: While this subroutine IS portable across *UI languages*, it does make an assumption that will hopefully hold for 
 all applications: that the "Edit" menu is the *4th* menu from the left (Apple menu, app menu, File, Edit).

 Examples:
     my pasteFromClipboard("") # paste into frontmost app
     my pasteFromClipboard("TextEdit")
*)
on pasteFromClipboard(targetProcess)
    tell application "System Events"
        if targetProcess is missing value or targetProcess = "" then
            set targetProcess to first process whose frontmost is true
        else
            if class of targetProcess is text then
                set targetProcess to process targetProcess
            end if
            -- Activate the application (make it frontmost), otherwise pasting will not work.
            set frontmost of targetProcess to true
        end if
        tell menu 1 of menu bar item 4 of menu bar 1 of targetProcess
            -- Find the menu item whose keyboard shortcut is Cmd-V
            set miPaste to first menu item whose value of attribute "AXMenuItemCmdChar" is "V" and value of attribute "AXMenuItemCmdModifiers" is 0
            click miPaste
        end tell
    end tell
end pasteFromClipboard
like image 153
mklement0 Avatar answered Oct 17 '22 18:10

mklement0


It isn't clear what your are looking for, but I'll do my best to offer some help.

If you leave send commented, then the message should already be open in Mail.app, waiting for further editing and sending.

By adding the line save newMessage, it will be saved to the drafts folder. The user can open it and continue editing whenever they please. If you want to actually send the draft from your application, use:

set sendMessage to first message of drafts mailbox
send sendMessage

Good luck!

like image 45
user2105505 Avatar answered Oct 17 '22 18:10

user2105505


I didn't see that you needed to edit the message before sending, so my previous answer was wrong. This time it should be correct.

It basically

  • takes a preformatted RTF file,
  • renders it & puts it into the clipboard,
  • creates a new message,
  • fills in the fields,
  • moves the focus to the message body,
  • pastes the formatted clipboard

Here is the code:

set textSubject to "HTML Test"
set toAddress to "[email protected]"
set toName to "John Doe"

tell application "Mail"
    do shell script "cat ~/Documents/RTF\\ File.rtf | textutil -stdin -stdout -convert rtf | pbcopy"

    set refMessage to make new outgoing message with properties {name:toName, address:toAddress, subject:textSubject, visible:true}
    tell refMessage
        make new to recipient at end of to recipients with properties {name:toName, address:toAddress}
    end tell
end tell

tell application "System Events"
    tell application process "Mail"
        set frontmost to true
        set value of attribute "AXFocused" of scroll area 4 of window textSubject to true
    end tell
    keystroke "v" using {command down}
end tell

Again, this worked fine on Snow Leopard

Hope that helped.

like image 1
Vic Avatar answered Oct 17 '22 19:10

Vic