Is it possible to send multiple emails to different people with Drafts?

I found an amazing script that converts my task written in my meeting note, in Drafts, to tasks in OmniFocus written by @clarke .

This is his script

// Process Meeting Notes

const taskPrefix = "OmniFocus: ";
const noteTag = "meeting notes";

// Function for removing the task prefix
function removeTaskPrefix(s) {
  var f       = (taskPrefix),
      r       = "",
      re      = new RegExp(f, "g"),
      matches = s.match(re);

  if (matches) {
    return s.replace(re,r);
  }
}

// Function to perform the callback url
function doCallbackURL(url, params) {
  var cb = CallbackURL.create();
  cb.baseURL = url;

  for(var key in params) {
   cb.addParameter(key, params[key]);
  }

  var success = cb.open();
  if (success) {
    console.log("Event created");
  } else {
    console.log(cb.status);
    if (cb.status == "cancel") {
      context.cancel();
    } else {
      context.fail();
    }
  }
}

// Scan for the task prefix in the draft
var d = draft.content;
var lines = d.split("\n");
var n = '';

for (var line of lines) {
  // If the line includes the task prefix, 
  // we remove exclude it from the final notes
  if (line.includes(taskPrefix)) {

    // Remove the trigger from the line
    var task = removeTaskPrefix(line);

    // OmniFocus URL Action
    doCallbackURL("omnifocus://x-callback-url/paste", {
      "content": task,
      "target": "inbox"
    });
  } else {
    n += line + "\n";
  }
}
draft.content = n;
draft.update();

// Send the note to Bear
doCallbackURL("bear://x-callback-url/create", {
  "text": draft.content,
  "tags": noteTag
});

When I’m holding a meeting, and we decide different actions for different people I like to send an email to everyone with their actions. The meeting always consists of the same people.

Is there a way to send the email directly from the note. I’m thinking something like this:

EA-A( Email Action for Anders): Move X
EA-A: Tell Y
EA-B (Email Action for Billy): Bring Z

Anders task will be added up and then sent to his email.
And the one task that Billy had would be sent directly!

Gratefully,
David

How do you write the tasks?

If you could add specific notation to each one (e.g. //bob at the end for tasks for Bob - which could be made easier with an action keyboard row), this would actually be easy. You would take the list of tasks and if it .endsWith('//bob') then you can replace this part and add it to the text, then if Bob has any tasks you email him. You’d add the tasks to variables for each person and then you have your email body.

I do tag the actions :slight_smile: So a typical meeting note would look something like this:

# Meeting - [[Date]]
### Attendees
Lars
Anders
David

## Notes

### 1. Barn animals
  Lars needs to buy more Pigs
  EA-L: Buy more pigs

### 2. Get a new barn door because the pigs are escaping
  EA-L: Buy a new barn door

### 3. Capture the escaped pigs
  EA-A: Contract a hunter to find the pigs.

### 4. What are we doing?
  OF: Think about changing career 

But if I understand you there would need to be 3 different actions, one for each person?

You could have it all parse in one. If I get a chance I’ll look into this!

That would be amazing :slight_smile: I’m going to work on it tonight! If I solve it I’ll let you know! :smiley:

I’m thinking about this, too. :slight_smile:

A couple of questions that come to mind that would influence the implementation are:

  1. Are there a finite number of email recipients? For example, would this be a hard-coded list within the script or should it be more dynamic?
  2. Is there a standard subject for the emails or would it be dynamic? You could use the meeting title or another aspect of the overall note or it could be specified in the line itself.

Depending on how these would be approached, the implementation and instruction would be more or less complex. For example, if there were a pre-determined list of recipients then they can be specified in the script and then when you use “EA-A”, that would be translated to one of the known email addresses.

In its most simplistic implementation, a line that states:

EA-A: Buy more pigs

Would be translated to:

————————
To: anders@example.com
Subject: Meeting ABC

Buy more pigs
————————

In a more complex, but flexible example, it could look something like:

EA-A: addr:anders@example.com subj:”New task” Buy more pigs

Which would be translated to:

————————
To: anders@example.com
Subject: New task

Buy more pigs
————————

What were you thinking about in terms of the interaction and flexibility?

Thanks,
Clarke

Hmm I did not think about the possibility to add new emails in the note itself, it adds a new level of usefulness. That would mean I could use it on different occasions when other people are present as well.

In a perfect world I think it would be a combination. The standard people in the meeting would have a "EA-X: " tag, this could be hardcoded. And if there are new people in the meeting you could add them in as "EA-anders@example.com: "

  1. For me the the meeting title would be fine as the subject, since it contains the date of the meeting for me or maybe add a task tag for search ability,

It probobly would look something like this:

EA-A: Buy more pigs
EA-A: Buy a new barn door

EA-Billy@example.com: Hunt pigs

And this would generate two emails

First:

To: anders@example.com
Subject: Meeting ABC - Tasks

Tasks:

  1. Buy more pigs
  2. Buy a new barn door

Second:

To: Billy@example.com
Subject: Meeting ABC -Tasks

Tasks:

  1. Hunt pigs

Ok, this should do what you’re looking for. I’ve tested it with the included use cases, along with some boundary cases and it appears to work in each case. In the event that the email is not in the pre-defined list or specified like EA-Billy@example.com, it will log that it couldn’t find the email.

The full script is:

// Process Meeting Notes

const taskPrefix = "OF: ";

const emailTrigger = "EA-";


// Pre-defined email recipients
const emailRecipients = {
  "A": "anders@example.com",
  "B": "person@example.com"
}

var emailRegexp = new RegExp(emailTrigger + "([^:]+): (.*)$");

// Turn EA-A: Buy more pigs
// into: ["anders@example.com", "Buy more pigs"]
function getEmailAndMessageFromLine(s) {
  var m = s.match(emailRegexp);

  if (m) {
    var emailKey = m[1];
    var message = m[2];

    if (emailKey.includes("@")) {
      emailAddress = emailKey;
    } else {
      if (emailKey in emailRecipients) {
        emailAddress = emailRecipients[emailKey];
      } else {
        console.log("Unable to find email address");
        return;
      }
    }

    console.log(emailAddress);
    console.log(message);
    return [emailAddress, message];
   } else {
    console.log("Unable to parse email address and message from line!");
  }
}

function removeLinePrefix(s, linePrefix) {
  var f       = (linePrefix),
      r       = "",
      re      = new RegExp(f, "g"),
      matches = s.match(re);

  if (matches) {
    return s.replace(re,r);
  }  
}

// Function for removing the task prefix
function removeTaskPrefix(s) {
  return removeLinePrefix(s, taskPrefix);
}

// Covenience function for performing callback urls
function doCallbackURL(url, params) {
  var cb = CallbackURL.create();
  cb.baseURL = url;

  for(var key in params) {
   cb.addParameter(key, params[key]);
  }

  var success = cb.open();
  if (success) {
    console.log("Event created");
  } else {
    console.log(cb.status);
    if (cb.status == "cancel") {
      context.cancel();
    } else {
      context.fail();
    }
  }
}

// Convenience function for sending email
function sendMail(recipient, subject, message) {

  if (recipient == null) {
    console.log("No recipient specified");
    context.fail();
  }

  var mail = Mail.create();
  
  mail.toRecipients = [recipient];
  mail.subject = subject;
  mail.body = message;
  
  var success = mail.send();
  if (!success) {
    console.log(mail.status);
    context.fail();
  }   
}

// Scan for the task prefix in the draft
var d = draft.content;
var lines = d.split("\n");
var n = '';

for (var line of lines) {
  // If the line includes the task prefix, 
  // we remove exclude it from the final notes
  if (line.includes(taskPrefix)) {

    // Remove the trigger from the line
    var task = removeTaskPrefix(line);

    // OmniFocus URL Action
    doCallbackURL("omnifocus://x-callback-url/paste", {
      "content": task,
      "target": "inbox"
    });
  } else if (line.includes(emailTrigger)) {
      var [emailAddress, message] = getEmailAndMessageFromLine(line);

     sendMail(emailAddress, draft.title + " - Tasks", message);
 } else {
    n += line + "\n";
  }
}
draft.content = n;
draft.update();

// Send the note to Bear
doCallbackURL("bear://x-callback-url/create", {
  "text": draft.content,
  "tags": "meeting notes"
});

It works! Your the man Clarke!

But it sends one email for each “EA-X” Action.

So :
EA-A: Buy more pigs
EA-A: Buy a new barn door

Generates two email, instead of one. Is it possible to gather them in one email?

I hadn’t thought of that, but combining them into one makes sense. I’ll play around with it a little more today. It should not be a big deal to combine them.

It was pretty easy to make the update to combine tasks into a single email. I also removed two leftover debug console.log statements. Here is the updated script:

// Process Meeting Notes

const taskPrefix = "OF: ";

const emailTrigger = "EA-";


// Pre-defined email recipients
const emailRecipients = {
  "A": "anders@example.com",
  "B": "person@example.com"
}

// Keep a list of tasks to be sent via email
var emailTasks = {}

var emailRegexp = new RegExp(emailTrigger + "([^:]+): (.*)$");

// Turn EA-A: Buy more pigs
// into: ["anders@example.com", "Buy more pigs"]
function getEmailAndMessageFromLine(s) {
  var m = s.match(emailRegexp);

  if (m) {
    var emailKey = m[1];
    var message = m[2];

    if (emailKey.includes("@")) {
      emailAddress = emailKey;
    } else {
      if (emailKey in emailRecipients) {
        emailAddress = emailRecipients[emailKey];
      } else {
        console.log("Unable to find email address");
        return;
      }
    }

   return [emailAddress, message];
   } else {
    console.log("Unable to parse email address and message from line!");
   }
}

function removeLinePrefix(s, linePrefix) {
  var f       = (linePrefix),
      r       = "",
      re      = new RegExp(f, "g"),
      matches = s.match(re);

  if (matches) {
    return s.replace(re,r);
  }  
}

// Function for removing the task prefix
function removeTaskPrefix(s) {
  return removeLinePrefix(s, taskPrefix);
}

// Covenience function for performing callback urls
function doCallbackURL(url, params) {
  var cb = CallbackURL.create();
  cb.baseURL = url;

  for(var key in params) {
   cb.addParameter(key, params[key]);
  }

  var success = cb.open();
  if (success) {
    console.log("Event created");
  } else {
    console.log(cb.status);
    if (cb.status == "cancel") {
      context.cancel();
    } else {
      context.fail();
    }
  }
}

// Convenience function for sending email
function sendMail(recipient, subject, message) {

  if (recipient == null) {
    console.log("No recipient specified");
    context.fail();
  }

  var mail = Mail.create();
  
  mail.toRecipients = [recipient];
  mail.subject = subject;
  mail.body = message;
  
  var success = mail.send();
  if (!success) {
    console.log(mail.status);
    context.fail();
  }   
}

// Scan for the task prefix in the draft
var d = draft.content;
var lines = d.split("\n");
var n = '';

for (var line of lines) {
  // If the line includes the task prefix, 
  // we remove exclude it from the final notes
  if (line.includes(taskPrefix)) {

    // Remove the trigger from the line
    var task = removeTaskPrefix(line);

    // OmniFocus URL Action
    doCallbackURL("omnifocus://x-callback-url/paste", {
      "content": task,
      "target": "inbox"
    });
    console.log("Task sent to OmniFocus");
  } else if (line.includes(emailTrigger)) {
      var [emailAddress, message] = getEmailAndMessageFromLine(line);

      if (emailAddress in emailTasks) {
      	emailTasks[emailAddress].push(message);
      } else {
        emailTasks[emailAddress] = [message];
      }
  } else {
    n += line + "\n";
  }
}

// Send email tasks, if any
for (var emailAddress in emailTasks) {
  var message = emailTasks[emailAddress].join("\n");
  
  sendMail(emailAddress, draft.title + " - Tasks", message);
  console.log("Email sent to: " + emailAddress);
}

draft.content = n;
draft.update();

// Send the note to Bear
doCallbackURL("bear://x-callback-url/create", {
  "text": draft.content,
  "tags": "meeting notes"
});
2 Likes

Here is yet another update. The difference with this one is that it first checks to see if the email recipient is in the pre-defined list. Previously, I was simply checking for an “@“ sign in the email key. Now you can have a pre-defined recipient like “anders@work” and “anders@home”. This may or may not be useful, but I figured why not.

On another note, this script is not validating any email addresses. That can be finicky so I chose to skip it. :slight_smile:

Also, I am going to setup a Github repo for these scripts, which will be better than constantly posting updates inline here.

// Process Meeting Notes

const taskPrefix = "OF: ";

const emailTrigger = "EA-";


// Pre-defined email recipients
const emailRecipients = {
  "A": "anders@example.com",
  "B": "person@example.com"
}

// Keep a list of tasks to be sent via email
var emailTasks = {}

var emailRegexp = new RegExp(emailTrigger + "([^:]+): (.*)$");

// Turn EA-A: Buy more pigs
// into: ["anders@example.com", "Buy more pigs"]
function getEmailAndMessageFromLine(s) {
  var m = s.match(emailRegexp);

  if (m) {
    var emailKey = m[1];
    var message = m[2];

    if (emailKey in emailRecipients) {
      emailAddress = emailRecipients[emailKey];
    } else if (emailKey.includes("@")) {
      emailAddress = emailKey;
    } else {
      console.log("Unable to find email address");
      return;
    }

    return [emailAddress, message];
  } else {
   console.log("Unable to parse email address and message from line");
  }
}

function removeLinePrefix(s, linePrefix) {
  var f       = (linePrefix),
      r       = "",
      re      = new RegExp(f, "g"),
      matches = s.match(re);

  if (matches) {
    return s.replace(re,r);
  }  
}

// Function for removing the task prefix
function removeTaskPrefix(s) {
  return removeLinePrefix(s, taskPrefix);
}

// Covenience function for performing callback urls
function doCallbackURL(url, params) {
  var cb = CallbackURL.create();
  cb.baseURL = url;

  for(var key in params) {
   cb.addParameter(key, params[key]);
  }

  var success = cb.open();
  if (success) {
    console.log("Event created");
  } else {
    console.log(cb.status);
    if (cb.status == "cancel") {
      context.cancel();
    } else {
      context.fail();
    }
  }
}

// Convenience function for sending email
function sendMail(recipient, subject, message) {

  if (recipient == null) {
    console.log("No recipient specified");
    context.fail();
  }

  var mail = Mail.create();
  
  mail.toRecipients = [recipient];
  mail.subject = subject;
  mail.body = message;
  
  var success = mail.send();
  if (!success) {
    console.log(mail.status);
    context.fail();
  }   
}

// Scan for the task prefix in the draft
var d = draft.content;
var lines = d.split("\n");
var n = '';

for (var line of lines) {
  // If the line includes the task prefix, 
  // we remove exclude it from the final notes
  if (line.includes(taskPrefix)) {

    // Remove the trigger from the line
    var task = removeTaskPrefix(line);

    // OmniFocus URL Action
    doCallbackURL("omnifocus://x-callback-url/paste", {
      "content": task,
      "target": "inbox"
    });
    console.log("Task sent to OmniFocus");
  } else if (line.includes(emailTrigger)) {
      var [emailAddress, message] = getEmailAndMessageFromLine(line);

      if (emailAddress in emailTasks) {
      	emailTasks[emailAddress].push(message);
      } else {
        emailTasks[emailAddress] = [message];
      }
  } else {
    n += line + "\n";
  }
}

// Send email tasks, if any
for (var emailAddress in emailTasks) {
  var message = emailTasks[emailAddress].join("\n");
  
  sendMail(emailAddress, draft.title + " - Tasks", message);
  console.log("Email sent to: " + emailAddress);
}

draft.content = n;
draft.update();

// Send the note to Bear
doCallbackURL("bear://x-callback-url/create", {
  "text": draft.content,
  "tags": "meeting notes"
});
2 Likes

I created a Github repo:

2 Likes