When I try to use myRtfString (an object with class ConcreteAttributedString) as the body of an email message, it is not accepted:
tell application “Mail”
set theMessage to make new outgoing message with properties {visible:true, content:myRtfString}
– the attempt above to use the NSConcreteAttributedString fails with an error:
(Mail got an error: Can’t get «class ocid» id «data optr00000000206CA30000600000».)
-- but a plain text string works as expected:
set theMessage to make new outgoing message with properties {visible:true, content:"a plain text string"}
A google search reveals that lots of people have run into this roadblock over the years. I have tried getting the rich text from the pasteboard without success. Has anyone found a solution?
In X-Code, I can load a file and extract myRtfString as an NSAttributedString and then I can use “sharing” to create a rich text email message:
This produces a “message” with the expected rich text content displayed in a new window in Mac Mail. The sharing service also supports setting the to: addresses, the subject, and it supports adding enclosures. BUT the documentation does not mention setting the cc: or bcc: fields, not does it mention how to set a specific sender account. It seems like there should be some way to expose these other message attributes to the NSSharingService interface in X-Code - any ideas how to do this?
After creating a rich text message with myMailShareService, I can get a reference to the new message using AppleScript by searching for its title in the Drafts mailbox. But the found object has class “message”, unlike the the object produced by Applescript above, which has class “outgoing message”. All the attributes of “message”, are read only - unlike an “outgoing message” where they are read/write. So how would I convert a “message” to an “outgoing message” so it can be edited (e.g. fill in the cc: field)?
You can’t pass a pointer («data optr…») to an application like that. But even if you convert it to the RTF as text format, it sadly won’t do what you want.
As for the limitations of NSSharingService, I suspect they are quite deliberate. My impression is that Apple sees Mail automation as a potential vector for malware, so the functionality is deliberately limited. If you want decent email automation, you really need to look at third-party email clients.
Thanks for the quick comment. I wish I could just convert the “message” created by NSSharingService to an “outgoing message” like the ones easily produced by AppleScript. But I don’t know if this is possible. In the mean time, I have resorted to NSSharingService to create the rich text content followed by GUI scripting to fix up the mail fields. There are some differences between OS 10.13 vs 10.14 that I don’t understand when calling procedures in various ways (Accessibility enabled), but the code below seems usable in both. (not sure why the code below is sometimes styled, sometimes in a scroll view ??).
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit" -- needed for used rtf methods and NSSharingService
------------------------- setup for testing -------------------------
set aFromAddress to "Joe Smith – joe@gmail.comm" -- with multiple mail accounts, specify the one to use as the sender (exact spelling using m-dash)
set aToAddress to "w@somewhereOrOther.comm"
set uniqueWindowName to "a unique msg subject/window-name"
set testValues to {mailTo:"whyChangeThis?", mailCc:"x@mailCc.Comm", mailBcc:"y@mailBcc.Comm", mailReplyTo:"z@mailReplyTo.Comm", mailSubject:"mailSubject", mailSender:aFromAddress}
(*
-- or construct a unique name for a new mail message window
property NSDate : a reference to current application's NSDate
set theData to NSDate's timeIntervalSinceReferenceDate
set uniqueWindowName to theData as string
*)
-- Mail must be active with GUI scripting enabled
tell application "System Events"
if not (exists of application process "Mail") then
display alert "Mail must be running with accessibility enabled in Secruity settings (to allow GUI scripting)"
return
end if
end tell
-- create a message for testing ----
-- BUT the appleScript translation below only works for OS 10.13.
-- In OS 10.14, create a Rich Text message using the NSSharingService code in objective-c
-- classes, constants, and enums used for getting the Rich Text
----- (https://forum.latenightsw.com/t/read-and-write-rtf-files/1200)
property NSData : a reference to current application's NSData
property NSAttributedString : a reference to current application's NSAttributedString
property NSDictionary : a reference to current application's NSDictionary
property NSString : a reference to current application's NSString
property NSRTFTextDocumentType : a reference to current application's NSRTFTextDocumentType
set posixPath to POSIX path of (choose file with prompt "Choose an RTF file" of type {"rtf"})
-- set posixPath to POSIX path of ((path to desktop folder as text) & "test.rtf")
-- read file as RTF data
set theData to NSData's dataWithContentsOfFile:posixPath
-- create attributed string from the data
set {theStyledString, docAttributes} to NSAttributedString's alloc()'s initWithRTF:theData documentAttributes:(reference)
if theStyledString is missing value then error "Could not read RTF file"
-- object-c NSSharingService code to create a Rich Text email - translated to applescript below (but the translation only works in OS 10.13)
(*
NSSharingService* myMailShareService = [NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail];
myMailShareService.subject = uniqueWindowName;
mailShare.recipients = @["denisd@sassafras.com"];
NSArray* shareItems = @[theStyledString];
[myMailShareService performWithItems:shareItems];
*)
property NSSharingService : a reference to current application's NSSharingService
set myMailShareService to NSSharingService's sharingServiceNamed:"com.apple.share.Mail.compose"
set subject of myMailShareService to uniqueWindowName
set recipients of myMailShareService to {aToAddress}
set shareItems to {theStyledString}
tell myMailShareService to performWithItems:shareItems
delay 1 -- now mail has a new Rich Text mail msg whose window title is uniqueWindowName
------------------------- end testing setup -------------------------
setMailFieldsInMsgWindow(testValues, uniqueWindowName)
on setMailFieldsInMsgWindow(mailFields, windowName)
try
mailTo of mailFields -- maybe testing the dictionary for the field name before attempting to pass its value makes thread or timing issues more predictable ?? when runing in script debugger??
my setValueForFieldInWindow(mailTo of mailFields, "To:", windowName) -- mailTo: field can be set by NSSharing, so typically doen't need to be replaced here (so not included in the mailFields dictionary)
end try
try
mailCc of mailFields
my setValueForFieldInWindow(mailCc of mailFields, "Cc:", windowName)
end try
try
mailBcc of mailFields
my setValueForFieldInWindow(mailBcc of mailFields, "Bcc:", windowName)
end try
try
mailReplyTo of mailFields
my setValueForFieldInWindow(mailReplyTo of mailFields, "Reply To:", windowName)
end try
try
mailSubject of mailFields
my setValueForFieldInWindow(mailSubject of mailFields, "Subject:", windowName)
end try
try
mailSender of mailFields
my setSenderChoice(mailSender of mailFields, windowName) -- an alert will be posted if there are sender choices displayed but the designated From: address is not available in the pop down menu
end try
end setMailFieldsInMsgWindow
on setValueForFieldInWindow(theValue, theFieldName, windowName)
local theFields, theField
tell application "System Events" to tell process "Mail"
if system version of (system info) < "10.14" then
set theFields to get every text field of splitter group 1 of window windowName
else
set theFields to get every text field of window windowName
end if
try
repeat with a from 1 to count of theFields
if name of item a of theFields is theFieldName then
set theField to item a of theFields
exit repeat
end if
end repeat
set value of theField to theValue
end try
end tell
end setValueForFieldInWindow
on setSenderChoice(mailSender, windowName)
local theSenderPopUp, theSenderMenu, theAccounts, a, theAccount
tell application "System Events" to tell process "Mail"
try
if system version of (system info) < "10.14" then
set theSenderPopUp to pop up button 1 of splitter group 1 of window windowName
else
set theSenderPopUp to pop up button 1 of window windowName
end if
-- set focused of theSenderPopUp to true
tell theSenderPopUp
click
set theSenderMenu to menu 1 of theSenderPopUp
set theAccounts to every menu item of theSenderMenu
set a to count of theAccounts
repeat until a = 0 or title of (item a of theAccounts) = mailSender
set a to a - 1
end repeat
if a > 0 then
set theAccount to menu item a of theSenderMenu
tell theAccount
click
end tell
else
display alert "Mail sender account \"" & mailSender & "\" not available."
end if
end tell
end try
end tell
end setSenderChoice
Here’s a script that will create an outgoing message in Mail styled with HTML.
Note: you can style the text (color/font/size), and control margin for elements but designating background colors and widths seem to be overridden by the default css file used by the Mail composer, OR… perhaps the passed HTML is being translated to RTF where “widths” and divs aren’t relevant?
use framework "Foundation"
use framework "AppKit"
use scripting additions
-- classes, constants, and enums used
property NSUTF8StringEncoding : a reference to 4
property NSSharingServiceNameComposeEmail : a reference to current application's NSSharingServiceNameComposeEmail
property NSAttributedString : a reference to current application's NSAttributedString
property NSString : a reference to current application's NSString
property NSSharingService : a reference to current application's NSSharingService
-- MAIL MESSAGE VALUES
set headlineText to "<p style=\"font-family:-apple-system,san-serif;font-size:64px;text-align:left;margin-bottom:0;\"></p>"
set bodyText to "<h1 style=\"font-family:-apple-system;\">This is a headline level 1</h1>
<h2 style=\"font-family:-apple-system;\">This is a headline level 2</h2>
<p style=\"font-family:Helvetica;font-size:16px;color:red;text-align:left;\">
Nulla vitae elit libero, a pharetra augue. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec sed odio dui.
</p>
<p style=\"font-family:Helvetica;font-size:16px;color:red;text-align:left;\">
Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Maecenas faucibus mollis interdum. Maecenas sed diam eget risus varius blandit sit amet non magna.
</p>
<p style=\"font-family:Helvetica;font-size:16px;color:red;text-align:left;\">
Maecenas sed diam eget risus varius blandit sit amet non magna. Nullam quis risus eget urna mollis ornare vel eu leo. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Etiam porta sem malesuada magna mollis euismod. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis.
</p>
<ol style=\"font-family:Helvetica;font-size:18px;color:orange;text-align:left;\">
<li style=\"font-family:Helvetica;font-size:18px;color:orange;text-align:left;\">First item</li>
<li style=\"font-family:Helvetica;font-size:18px;color:orange;text-align:left;\">Second item</li>
<li style=\"font-family:Helvetica;font-size:18px;color:orange;text-align:left;\">Third item</li>
</ol>"
set messageSubject to "HOWDY"
set recipientAddresses to {"johnny-appleseed@apple.com"}
-- THE HTML FOR THE MESSAGE
set thisHTML to "<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\" />
<title>MAIL TEMPLATE</title>
</head>
<body style=\"margin:0;width:100%;Height:100vh;\">" & ¬
headlineText & return & ¬
bodyText & return & ¬
"</body>
</html>"
set theSource to NSString's stringWithString:thisHTML
set theData to theSource's dataUsingEncoding:NSUTF8StringEncoding
set anAttributedString to NSAttributedString's alloc()'s initWithHTML:theData documentAttributes:{}
-- USE THE MAIL SHARING SERVICE TO CREATE A NEW MAIL MESSAGE
set aSharingService to NSSharingService's sharingServiceNamed:(NSSharingServiceNameComposeEmail)
if aSharingService's canPerformWithItems:{"someone@somewhere.com"} then
set aSharingService's subject to messageSubject
set aSharingService's recipients to recipientAddresses
tell aSharingService to performSelectorOnMainThread:"performWithItems:" withObject:{anAttributedString} waitUntilDone:false
end if
I note that changing “bob@nowhereville.com” to one of my email addresses doesn’t affect the outcome – is there a way to get it to select a specific one of my email addresses?
Its also possible to do everything Sal script does, with 1 line of code in do shell script.
textutil -convert webarchive textToSend.rtf -output textToSend.webarchive
Shane I’m not sure Apple will support webarchive in feature, they remove it from WebView from WebKit… but I believe its still possible to use pasteboard.
I could also share other discover of mine in web inspector in Safari.
In the element tab, there is icon (print) it force the frame to force print media style.
It could make a big difference, then a website is saved to PDF.
OneNote from Microsoft use HTML input to create a document/note so its possible to create a document page and share it to Apple Mail. The sad thing it doesn’t support AppleScript. It could be a quick way to make a email to be more powerful and what Apple mail provide. And with the Microsoft cloud feature its simple to create templates in OneNote to be
used anywhere.
The address should look like this: “your name <you@yourdomain.com>” (don’t miss the <>characters)
The best way to get it is to make manually a new mail and then run this snippet in SD:
tell application "Mail" to return sender of outgoing message 1
Then copy the script’s result and paste it in this second script to replace resultOfPreviousScipt:
tell application "Mail"
activate
set newMail to make new outgoing message
tell newMail
set visible to true
set sender to "resultOfPreviousScipt"
end tell
end tell
Answer the question I came with - how to send the email rather than leave it open. To the end of the script in post #4 above (sorry, my forum privs don’t let me link to it) add this code, paying attention to the need to Accessibility prefs for the app/process running the script:
-- IMPORTANT, for the next part to work add the 'Mail' app (and the app running the script, e.g. 'Script Editor'
-- to the Accessibility prefs under the Security & Privacy part of System Preferences
activate application "Mail"
tell window messageSubject of application "Mail"
tell application "System Events"
tell process "Mail"
-- insert GUI Scripting statements here:
click button 1 of toolbar 1 of window messageSubject
end tell
end tell
end tell