Action
Emails to Draft
UPDATES
about 2 months ago
Fixed email link finally
about 2 months ago
Fixed email link finally
2 months ago
Fixed the return/linefeed error again
2 months ago
Replaced the shell script which appears to fix the beach-balling delay.
2 months ago
Updating the name
2 months ago
Fix text and MarkDown
2 months ago
Converted the output to Markdown
2 months ago
Fixed the Long Title bug through Return character handling
Changed the URL encoding to use AppleScript instead of Python for eventual complete removal of Python
Removed Temporary file cleanup to try and speed up the action
Changed OpenAI model to gpt-4o for speed
2 months ago
Here’s an enumeration of the changes and improvements between V1 and V4 of the email thread processor script:
Improved removeQuotedText function:
- Added a more sophisticated quote detection system using patterns
- Introduced a quoteHeaderPattern to identify and skip quote headers
- Improved handling of different quote formats
New helper functions:
- Added a trim function to remove leading and trailing whitespace
- Introduced a matchesPattern function for regex-like pattern matching
Enhanced email processing:
- Now includes the creation of message links for each email in the thread
- Improved formatting of email details in the threadContent
Cleanup functionality:
- Added a cleanupTempFiles function to remove temporary files created during processing
Error handling:
- Improved error handling and user feedback throughout the script
Prompt improvements:
- Updated the prompt text with more detailed instructions
- Added a requirement to include section headings even for empty sections
File handling:
- Improved temporary file naming convention (using “email_processor_” prefix)
- Better management of file paths and cleanup
Script structure:
- Reorganized the script into more clearly defined functions
- Added comments to improve code readability and maintainability
Execution flow:
- Added an execute function to encapsulate the main script logic
- The script now runs the execute function automatically when launched
Minor adjustments:
- Updated variable names for clarity
- Improved string concatenation and formatting throughout the script
Compatibility:
- Explicitly mentioned compatibility with modern MacOS and Python3
These improvements make the V4 script more robust, efficient, and user-friendly compared to the V1 version, with better handling of email threads and improved information extraction capabilities.
2 months ago
Here’s an enumeration of the changes and improvements between V1 and V4 of the email thread processor script:
Improved removeQuotedText function:
- Added a more sophisticated quote detection system using patterns
- Introduced a quoteHeaderPattern to identify and skip quote headers
- Improved handling of different quote formats
New helper functions:
- Added a trim function to remove leading and trailing whitespace
- Introduced a matchesPattern function for regex-like pattern matching
Enhanced email processing:
- Now includes the creation of message links for each email in the thread
- Improved formatting of email details in the threadContent
Cleanup functionality:
- Added a cleanupTempFiles function to remove temporary files created during processing
Error handling:
- Improved error handling and user feedback throughout the script
Prompt improvements:
- Updated the prompt text with more detailed instructions
- Added a requirement to include section headings even for empty sections
File handling:
- Improved temporary file naming convention (using “email_processor_” prefix)
- Better management of file paths and cleanup
Script structure:
- Reorganized the script into more clearly defined functions
- Added comments to improve code readability and maintainability
Execution flow:
- Added an execute function to encapsulate the main script logic
- The script now runs the execute function automatically when launched
Minor adjustments:
- Updated variable names for clarity
- Improved string concatenation and formatting throughout the script
Compatibility:
- Explicitly mentioned compatibility with modern MacOS and Python3
These improvements make the V4 script more robust, efficient, and user-friendly compared to the V1 version, with better handling of email threads and improved information extraction capabilities.
ProjectToDraft
A Draft AppleScript that takes a highlighted email thread in the Mail App, extracts the important information for a project, and creates a new Draft out of it.
Code Versions: https://github.com/ddegner/ProjectToDraft/tree/main
Detailed writeup: https://www.daviddegner.com
Overview
This AppleScript takes the selected email thread in the macOS Mail app and sends it to OpenAI’s API for processing to extracts key information. The output is saved in a new draft, making it easy to organize and summarize email exchanges with clients.
This script is designed specifically for photographers who need to keep track of client communication.
Features
- Integrates with OpenAI API: Extracts and formats key information from emails using OpenAI’s API.
- Removes Quoted Text: Eliminates quoted text from email threads to prevent redundant information.
- Email Analysis: Extracts important details such as client information, project timeline, and deliverables.
- Drafts App Integration: Saves the processed response as a new draft in the Drafts app for easy reference.
Dependencies
- OpenAI API Key: You need an OpenAI API key to access GPT-4. Store this key in your macOS Keychain under the label
OpenAI_API_Key
.sh security add-generic-password -a "<username>" -s "OpenAI_API_Key" -w "<YOUR_OPENAI_API_KEY>"
- macOS Mail Application: The script works with email threads selected in the macOS Mail app.
- Python 3: Required for making API calls. Python 3 is pre-installed on most modern macOS systems.
- Drafts Application: The script creates a new draft in the Drafts app, which must be installed and set up for automation.
Installation
- Set Up OpenAI API Key: Store your OpenAI API key in macOS Keychain using the command above.
- Install Python 3: If it’s not already installed, use Homebrew:
sh brew install python
- Create a new action in Drafts: In that action, add a Run AppleScript step. Paste the AppleScript into the script window. Uncheck all visibility in iOS because unfortunately, this only runs on macOS.
- Grant Action Permissions: When running the first time, it will likely ask for permissions to access the Mail app and the filesystem because it has to write a temporary text file.
Getting an OpenAI API Key
To use this script, you’ll need an OpenAI API key. Here’s how to get one:
- Sign Up or Log In: Go to OpenAI’s website and sign up for an account, or log in if you already have one.
- Create an API Key: Navigate to the API keys section in your OpenAI account settings. Click on Create new secret key.
- Copy the Key: Once created, copy the API key. Be sure to store it securely, as you’ll need it for configuring this script.
- Add to Keychain: Store your API key in your macOS Keychain with the following terminal command:
sh security add-generic-password -a "<username>" -s "OpenAI_API_Key" -w "<YOUR_OPENAI_API_KEY>"
Usage
- Select Emails: Open the macOS Mail app and select the email thread you want to analyze.
- Run the Drafts Action: The script will:
- Extract key information from the selected email thread.
- Use OpenAI’s API to summarize the details.
- Save the formatted output in a new draft in the Drafts app.
- Customize the Output: The output is organized into sections and can be easily edited by modifying the ChatGPT prompt.
Adjusting the Prompt for Your Project
The default prompt used in this script is designed for general email analysis, but you can easily adjust it to fit the specific needs of your project. Here’s how to do it:
- Locate the Prompt in the Script: Find the section of the script where the prompt is defined. It will typically look something like this:
applescript set promptText to "I am David Degner, the photographer, and this is an email thread with my client. Please extract the following information from the email thread for me..."
- Modify the Prompt: Adjust the text to include specific details related to your project. For example, if you’re a designer, you could modify it to say:
applescript set promptText to "I am [Your Name], a graphic designer, and this is an email thread with my client. Please extract the following design-related information from the email thread..."
- Tailor Sections: You can also change the sections you want the output to include. For example, if you’re working on a marketing campaign, you might want to add sections like “Campaign Goals” or “Target Audience”.
- Test the Changes: Run the script with a sample email thread to ensure the adjusted prompt provides the output you need.
Script Details
Key Functions
execute()
: The main function that retrieves selected emails, processes them, and sends a request to OpenAI.removeQuotedText(emailBody)
: Cleans the email body by removing quoted sections to avoid redundant content.
Error Handling
- Keychain Retrieval: Displays an alert if the OpenAI API key is not found in Keychain.
- API Call Errors: Alerts the user if the API request fails.
- Email Selection: Alerts the user if no emails are selected.
Security Considerations
- Sensitive Information: The OpenAI API key is stored in macOS Keychain for security. Only use the script on trusted devices.
- Internet Access: An internet connection is required to call the OpenAI API.
License
This script is open source and available under the MIT License.
Contributing
Feel free to fork the repository and submit pull requests. Contributions are welcome to enhance functionality, improve error handling, or add support for other email clients or automation tools.
Troubleshooting
- No Response from API: Ensure your internet connection is stable and that the API key is correctly added to Keychain.
- Permission Issues: Make sure you have granted automation permissions to Mail, Terminal, and Drafts in System Preferences > Security & Privacy.
For questions or assistance, please open an issue on the github repository: https://github.com/ddegner/ProjectToDraft/tree/main
Steps
-
runAppleScript (macOS only)
-- Email Thread Processor for Drafts -- This AppleScript runs as an action in the Drafts app it processes selected email threads from Mail, -- pulls out the imporant information using OpenAI's GPT-4o, creates a new draft in the Drafts app with the -- extracted information and original thread content, and includes links to each original message in the Mail app. -- It runs on a modern MacOS with a bit of Python3. use framework "Foundation" use scripting additions -- Function to remove quoted text from an email body on removeQuotedText(emailBody) set nsEmailBody to current application's NSString's stringWithString:emailBody -- Regular expression patterns to match quoted text set patterns to {"(?m)^(>.*)$", "(?s)^On .*? wrote:.*", "(?m)^-----Original Message-----.*", "(?m)^From:.*", "(?m)^Sent:.*", "(?m)^To:.*", "(?m)^Subject:.*"} repeat with pattern in patterns set regex to current application's NSRegularExpression's regularExpressionWithPattern:pattern options:0 |error|:(missing value) set nsEmailBody to regex's stringByReplacingMatchesInString:nsEmailBody options:0 range:{0, nsEmailBody's |length|()} withTemplate:"" end repeat return nsEmailBody as string end removeQuotedText -- Function to replace characters in a string on replace_chars(theText, searchString, replacementString) set AppleScript's text item delimiters to searchString set theItems to text items of theText set AppleScript's text item delimiters to replacementString set theText to theItems as string set AppleScript's text item delimiters to "" return theText end replace_chars -- Helper function to trim whitespace from a string on trim(someText) set nsText to current application's NSString's stringWithString:someText set trimmedText to nsText's stringByTrimmingCharactersInSet:(current application's NSCharacterSet's whitespaceAndNewlineCharacterSet()) return trimmedText as string end trim -- Helper function to URL-encode a string on urlEncode(inputString) set nsString to current application's NSString's stringWithString:inputString set allowedChars to current application's NSCharacterSet's URLQueryAllowedCharacterSet() set encodedString to nsString's stringByAddingPercentEncodingWithAllowedCharacters:allowedChars return encodedString as string end urlEncode -- Function to create a message link for a given message on createMessageLink(theMessage) tell application "Mail" set messageId to message id of theMessage set messageSubject to subject of theMessage end tell set messageLink to "message://%3c" & messageId & "%3e" set markdownLink to "[" & messageSubject & "](" & messageLink & ")" return markdownLink end createMessageLink -- Function to write text to a file using ASObjC on writeToFile(theText, theFilePath) try set theNSString to current application's NSString's stringWithString:theText set theNSData to theNSString's dataUsingEncoding:(current application's NSUTF8StringEncoding) theNSData's writeToFile:theFilePath atomically:true return true on error errMsg display alert "Failed to write to file: " & errMsg return false end try end writeToFile -- Function to retrieve API key from Keychain on getAPIKeyFromKeychain(keyName) try set apiKey to do shell script "security find-generic-password -w -s " & quoted form of keyName return apiKey on error return missing value end try end getAPIKeyFromKeychain -- Function to sort messages by date using a custom sort on sortMessagesByDate(messageList) set sortedMessages to messageList set messageCount to count of sortedMessages tell application "Mail" repeat with i from 1 to (messageCount - 1) repeat with j from (i + 1) to messageCount set messageI to item i of sortedMessages set messageJ to item j of sortedMessages set dateI to date received of messageI set dateJ to date received of messageJ if dateI > dateJ then -- Swap the messages set item i of sortedMessages to messageJ set item j of sortedMessages to messageI end if end repeat end repeat end tell return sortedMessages end sortMessagesByDate -- Function to call OpenAI API using Python on callOpenAIAPI(apiKey, promptFilePath) set pythonScript to " import json import urllib.request import urllib.error import sys api_key = '" & apiKey & "' with open('" & promptFilePath & "', 'r', encoding='utf-8') as f: prompt = f.read() messages = [ {\"role\": \"user\", \"content\": prompt} ] payload = { \"model\": \"gpt-4o\", \"messages\": messages } headers = { \"Content-Type\": \"application/json\", \"Authorization\": f\"Bearer {api_key}\" } req = urllib.request.Request(\"https://api.openai.com/v1/chat/completions\", data=json.dumps(payload).encode('utf-8'), headers=headers) try: with urllib.request.urlopen(req) as response: result = json.loads(response.read()) print(result['choices'][0]['message']['content']) except urllib.error.HTTPError as e: error_info = e.read().decode('utf-8') print(f\"API request failed: {error_info}\") sys.exit(1) " try set apiResponse to do shell script "/usr/bin/python3 -c " & quoted form of pythonScript return apiResponse on error errMsg return errMsg end try end callOpenAIAPI -- Function to execute the main script on execute() try tell application "Mail" -- Get the selected messages from Mail set selectedMessages to selection if selectedMessages is {} then display alert "No email selected" message "Please select an email thread." buttons {"OK"} default button "OK" return end if -- Sort the messages by date received set sortedMessages to my sortMessagesByDate(selectedMessages) -- Initialize variables for thread content set threadContent to "" -- Process the messages in sorted order repeat with eachMessage in sortedMessages -- Get email details set emailSender to sender of eachMessage set emailSubject to subject of eachMessage set emailDate to date received of eachMessage set emailBody to content of eachMessage -- Remove quoted text set cleanedBody to my removeQuotedText(emailBody) -- Create and add the message link set messageLink to my createMessageLink(eachMessage) -- Append email details to threadContent set threadContent to threadContent & "From: " & emailSender & " / Subject: " & emailSubject & " / Date: " & emailDate & linefeed & cleanedBody & linefeed & "Message Link: " & messageLink & linefeed & "---" & linefeed & linefeed end repeat end tell -- Combine the prompt and the thread content set promptText to "I am David Degner, the photographer, and this is an email thread with my client. Please extract the following information from the email thread for me. Focus on the most relevant and clear information for each section, using explicit details mentioned in the emails. Ensure that all detailed information is included and is accurate. Here is the format to follow: - Format in markdown - The first line should be the project title, written directly without a heading. - Subsequent sections should include a heading. - If a section has no relevant information only write the heading and leave the section blank. CLIENT INFORMATION: Include the client or company requesting the work, with their main contact and role if available and relevant contact details such as email or phone numbers. Do not label each piece of information. AGENCY: Mention any agency or intermediary company involved, if applicable, including their name and relevant contact information. PROJECT TIMELINE: Extract relevant dates, including deadlines, shoot dates, or delivery timelines. LOCATION: Where exactly will the photography take place or what is the address of the client. PROJECT DESCRIPTION: Summarize the key objectives and scope of the project. Include any style, goals, or focus areas mentioned. ART DIRECTION: Highlight any references to stylistic direction, art direction, or creative requirements. DELIVERABLES: List the required outputs, such as final images, edited photos, reports, or videos. Include quantity, format, and deadlines. BUDGET: Extract all mentions of costs, estimates, or budgets. Look for terms such as 'budget', 'cost', 'estimate', 'fee', 'pricing', 'cost breakdown', 'quote', 'rate', or any mention of monetary values (e.g., '$500', 'USD', 'total cost'). Ensure every detail about finances is captured, even if mentioned indirectly. TEAM AND ROLES: Identify any other team members mentioned (e.g., assistants, models, makeup artists) and their roles. LICENSING AND USAGE RIGHTS: Include any mentions of licensing terms, usage rights, or agreements on how the photos will be used. REVISIONS OR FEEDBACK: Extract any details regarding rounds of revisions, feedback processes, or client approval steps. SPECIAL REQUIREMENTS: Mention any additional or unique requirements related to the shoot (e.g., equipment, props, permits, travel arrangements). EXTRA NOTES: Capture any additional information, such as meeting schedules, important discussions, or extra tasks. Make sure the project title appears as the first line without a label, and the remaining sections follow with clear headings and spacing." set fullPrompt to promptText & linefeed & linefeed & "Email Thread Content:" & linefeed & threadContent -- Generate a unique temporary file path set promptFilePath to do shell script "mktemp /tmp/email_processor_prompt.XXXXXX" -- Write the full prompt to the temporary file using the updated writeToFile function my writeToFile(fullPrompt, promptFilePath) -- Retrieve the OpenAI API key from Keychain set openAIAPIKey to my getAPIKeyFromKeychain("OpenAI_API_Key") if openAIAPIKey is missing value then display alert "API Key Not Found" message "Please store your OpenAI API Key in the Keychain." buttons {"OK"} default button "OK" return end if -- Use Python3 to make the API request set apiResponse to my callOpenAIAPI(openAIAPIKey, promptFilePath) if apiResponse starts with "API request failed:" then display alert "API Error" message apiResponse buttons {"OK"} default button "OK" return end if -- Create a new draft in Drafts app with the extracted information and original thread content tell application "Drafts" set fullContent to my replace_chars(apiResponse, return, linefeed) & linefeed & linefeed & "------------------" & linefeed & my replace_chars(threadContent, "return", linefeed) make new draft with properties {content:fullContent, flagged:false, tags:{"1 commercial"}} end tell on error errMsg number errNum display alert "An error occurred: " & errMsg & " (Error " & errNum & ")" end try end execute -- Run the execute function execute()
Options
-
After Success Default Notification Info Log Level Info