Getting Outlook Emails into Microsoft Planner: Native Options, Power Automate, and Send to Planner

Stressed woman at desk with two computer monitors showing spreadsheets and emails, three coworkers laughing behind her in open office

A common Microsoft 365 workflow starts with a simple question:

How do I get an Outlook email into Microsoft Planner?

There are several possible answers, depending on what “into Planner” means.

You may only need a personal reminder. You may want an automated process that handles every matching email the same way. Or you may need to review each message and decide where the resulting Planner task belongs.

The main options are:

  1. Flag the email.
  2. Manually create a Planner task.
  3. Build a Power Automate flow.
  4. Use an Outlook add-in such as Send to Planner.

Flagging the Outlook Email

The easiest native option is to flag the message in Outlook.

Flagged emails can appear in Microsoft To Do and within the task views available through the newer Microsoft Planner experience. This works well when the goal is simply to remind yourself to return to a message.

However, flagging does not create a normal task in a selected Planner plan.

You cannot use the flag itself to choose:

  • A shared Planner plan
  • A bucket
  • An assignee
  • Start and due dates
  • Selected portions of the email
  • Specific attachments

Flagging is therefore best suited to personal follow-up rather than team task management.

Manually Creating the Planner Task

The next option is to open Planner and create the task manually.

This gives you full control, but it also means moving between Outlook and Planner and copying the relevant information yourself.

Depending on the email, that may include:

  • Rewriting the subject as a task title
  • Copying part of the message body
  • Saving and uploading attachments
  • Assigning the task
  • Selecting a bucket
  • Adding dates
  • Preserving a reference to the original email

Manual entry is reasonable when this only happens occasionally. It becomes repetitive when email is a regular source of work.

Using Power Automate

Power Automate is often the first recommendation for connecting Outlook and Planner.

A flow can create a Planner task when an email:

  • Arrives from a particular sender
  • Contains certain text
  • Is moved into a folder
  • Is assigned a category
  • Meets other predefined conditions

This is a good solution when the workflow is consistent.

For example, if every message sent to a particular mailbox should create a task in the same plan and bucket, Power Automate may be the correct tool.

The challenge is that many email-to-task decisions are not consistent.

One email may need to go to an operations plan. The next may belong to a customer project. Another may only require one paragraph from a long thread. Some attachments may be useful while others are signatures, logos, or unrelated files.

The user may need to decide, for each message:

  • Which plan to use
  • Which bucket to use
  • Who should be assigned
  • What the task title should be
  • Which part of the message to include
  • Which attachments to include
  • Whether a start or due date is needed
  • Whether the message came from a personal or shared mailbox

Power Automate can support sophisticated workflows, but an interactive process with many optional decisions can require a substantial amount of configuration.

At that point, the flow is no longer simply “when an email arrives, create a task.”

It becomes a small application.

Why I Built Send to Planner

I originally built Send to Planner because I regularly encountered emails that needed to become Planner tasks, but they did not all follow the same rule.

The problem was not merely creating the task. The problem was deciding what the task should contain and where it should go.

Send to Planner is an Outlook add-in that lets the user make those decisions while viewing the email.

The user can select options such as:

  • Planner plan
  • Bucket
  • Task title
  • Assignee
  • Start date
  • Due date
  • Email body content
  • Attachments
  • A reference to the original message

This makes it possible to create a complete Planner task without moving back and forth between Outlook and Planner.

The sent email inside Microsoft Planner
Email message fidelityAll attachments and email as PDF

It also avoids forcing every email through the same predefined automation.

Choosing the Right Approach

Each method has a valid use case.

MethodBest suited for
FlaggingPersonal reminders and follow-up
Manual entryOccasional task creation
Power AutomateRepetitive workflows with consistent rules
Send to PlannerInteractive task creation where each email may be different

The question is not whether Power Automate or an Outlook add-in is universally better.

The better question is:

Does the same rule apply to every email?

When the answer is yes, Power Automate is often a strong solution.

When the answer is no, an interactive Outlook add-in may be more practical.

Security and Microsoft 365 Administration

Third-party Outlook add-ins may require approval from a Microsoft 365 administrator, especially in managed organizational environments.

That is a reasonable concern.

The Send to Planner product page includes information covering:

  • Requested Microsoft permissions
  • Data access
  • Data flow
  • Privacy
  • Security
  • Administrative deployment

Send to Planner is also going through the Microsoft 365 App Certification process.

Certification is not a substitute for an organization’s own security review, but it provides an additional level of independent validation. I am also willing to work directly with Microsoft 365 administrators or security teams that need more information before approving the add-in.

Learn More

For a detailed comparison of the available methods, including flagged email, manual task creation, Power Automate, and Send to Planner, see:

How to Get an Outlook Email into Microsoft Planner

For product details, features, security information, and installation options, see:

Send to Planner for Microsoft Outlook

The complete user guide is also available here:

Send to Planner User Guide

How Content Security Policy Affects Office Add-ins

I now have several add-ins in AppSource and all doing well. But one in particular is getting a LOT more attention than others: “Send to Planner.” While popular with the general public, IT admins are not because it needs so many permissions. I wrote a fairly complex explanation to why it needs to many permissions: https://kryl.com/?page=kb&id=31. Ok, so some say – fine, I trust that, but others want more, a lot more, like my latest Penetration Testing Results. They also ask for more than attestation and wonder why I am not on the Office 365 certified list… me too, it turns out…

Sidebar first… This is leading somewhere, I promise… So, Office 365 Certification… I jump through God knows how many hoops to try to get my Office 365 certification rather than just a generic Publisher Attestation. I have to provide a TON of documentation and one of the artifacts is a good penetration test which are actually a LOT of work. Anyway, after a few weeks of gathering information, study on how to do this, that or the other thing, a ton of back-and-forth in email… I was told…

“Well, so, yeah… Your company is… yeah… just not big enough for us to consider you.”

So, when I submitted the VERY FIRST FORM with employee count you still let me proceed! I waste weeks, collecting and finagling and answering to artifacts and this… Ugh! Anyway, it was an educational experience and from that, I learned how to create a good Pen Test document.

…Back to the main attraction. Turns out to make administrators HAPPY, you need to have a really good Pen Test and one of the picker areas they remark upon is your sites Content Security Policy. I use an NGINX server in Azure. It took a LOT of finagling to get it restrictive enough for me to score an A+ on https://securityheaders.com:

script-src 'self' https://p.trellocdn.com https://alcdn.msauth.net https://cdn.msftauth.net https://login.microsoftonline.com https://login.live.com https://*.trello.com https://*.asana.com https://appsforoffice.microsoft.com;

In my QA testing, everything seems to be humming along with my A+ except that I started noticing my add-in failing to load sometimes. Pop open the F12 developer tools and viola, a MicrosoftAjax.js error that is failed to load because of my CSP. So, I added it:

script-src 'self' https://p.trellocdn.com https://alcdn.msauth.net https://cdn.msftauth.net https://login.microsoftonline.com https://login.live.com https://*.trello.com https://*.asana.com https://appsforoffice.microsoft.com https://ajax.aspnetcdn.com;

That seemed to make it happy, and my QA succeed so I published. I had figured it must be something in my code, but I did not find Ajax in any of my dependencies or my code base, so I gathered it was from office-js (rightly so), but no big deal, right. I did an NPM UPDATE and grabbed another coffee.

But then users started to contact me that they were unable to use the add-in. Why? Well, turns out in certain conditions my code did something that would fail. And after a lot of troubleshooting, I start to see this error around areas where I am getting mailbox session data:

Cannot read properties of undefined (reading ‘cannotDeserializeInvalidJson’)
at Sys.Serialization.JavaScriptSerializer.deserialize

So, oh boy… 2015 wet sock moment… the Microsoft Office JS library apparently injects the MicrosoftAjax.js library right into your taskpane page and uses it for JSON.parse(). What?

Send to Planner (and my other add-ins) were all showing this. And I had NEVER seen this Ajax error before — so it was new. I scoured the web, asked my new buddy Chat GPT and… long story short, because this was something new… What did I do… Pen Testing… Yes, and what did I recently change… Oh, yeah, my CSP. Yes… more digging, trial and error:

script-src 'self' 'unsafe-eval' 'unsafe-inline' https://p.trellocdn.com https://alcdn.msauth.net https://cdn.msftauth.net https://login.microsoftonline.com https://login.live.com https://*.trello.com https://*.asana.com https://appsforoffice.microsoft.com https://ajax.aspnetcdn.com;

Well, now I only have an A on security headers, and I get a nice little blurb about:

This policy contains ‘unsafe-inline’ which is dangerous in the script-src directive. This policy contains ‘unsafe-eval’ which is dangerous in the script-src directive.

Inline is one thing and good use of input sanitization with DomPurify (thanks MichaelZ), corner cases that. But unsafe-eval? I confirmed – on/off/on/off – yep… it needs to be there.

Anyway, it was a long journey – many hours – and I am wiser for it. I know this is probably because OfficeJS still supports versions back to Office 2013 and Edge before it went the path of the Chromium, but I hope maybe it can move away from needing eval() code and the MicrosoftAjax.js library in the near future and I can add a (+) back to my security posture.

Hopefully, this post will help others from tripping up on this as they lock things down.

403 Error with Planner API

I am posting this here as it is a vexing issue, and I have received no feedback or guidance on it.

If you use Power Automate with the Planner Graph API or you have developed a solution that used the Planner Graph API to create tasks you will be affected by this problem:

https://feedbackportal.microsoft.com/feedback/idea/91794c73-0e87-ef11-9442-6045bdb4f28f

Essentially, the issue is that the new PREMIUM Plans that you can create from Teams Planner App (and soon the updated Planner Website), are not fully supported in the Graph API.

When you try to POST (create) a task for example you get a 403 error. This would imply that you need some type of Graph API Permission in your Entra ID Registration, but there is NO DOCUMENTATION on this at all.

If you or your organization use Microsoft Planner and have recently upgrade to Premium plans, and you automate your plans, please UP VOTE the item reference above.

If you are using Power Automate, here is a simple one I created. If a task is created and assigned to you anywhere in Planner, it will create a new task for you on a specified plan in a specified bucket.

  1. If I run this, it creates a new task on the Plan: “Starts of Basic”
  2. If I upgrade the plan to PREMIUM and then go to any other plan and create a task, and the automation runs, this is what I get:

This is the error you get:

{
"error": {
"code": "ArchivedEntityCanNotBeUpdated",
"message": "Archived entity can't be modified.",
"innerError": {
"date": "2024-10-22T13:33:52",
"request-id": "f6cbf471-eead-49e8-944c-c3d9f9b00147",
"client-request-id": "f6cbf471-eead-49e8-944c-c3d9f9b00147"
}
}
}

Pocket a Card v1.07

It has been a while since I have shown any love to this Power-Up. It has a LOT of users that use it a LOT. And unfortunately, for me they use the FREE features only. It might be a bit draconian, but with nearly 75,000 website hits per week from this one Power-Up alone, I added a retro-active limitation of only 5 pocket cards per board without a subscription. But I added a LOT to the subscription. Now you get all these features:

  • Epic cards, with epic covers and the ability to track resources, cost, budget, completeness, priority and deadline date.
  • You also have the ability to set cards to be pocketed at a future date.
  • Additionally, you can set cover colors, and
  • have more than 5 Pocket or Epic cards on the board.

Additional enhancement includes a bit of a redesign on the UI (star on the default pocket card), and more options on the back of the card. If these update garner more subscribers, I have even more planned:

  • Custom fields on Epics
  • Better pocketed card preview
  • Ability to edit the name, description or add comments to pocketed cards
  • Ability to select cards to pocket from the board, from the back of a pocket card, and
  • this includes searching for cards and adding them as well.
  • And much, much, more.

But I need more subscribers first. Please check it out: Pocket a Card

Horrible Safari Issue

Somebody will probably tell me that I am doing something wrong, and that Safari is behaving correctly, but, uh… since all other browsers work fine, and it ONLY happens in Safari, I will call it a horrible Safari issue. Now that disclaimers are aside…

I looked ALL over the Internet to find a reference to this issue, I searched for days and days, I eventually abandoned all hope and started talking to my volleyball when suddenly I ran into this:

I was startled, confused, there was a sign that said, “The End.” And as I was looking around there was Vincent D’Onofrio beside me with a wine glass and a rifle and I could not tell which character he was in at that moment.

Ok, ok, I am not going to explain that — some 1999 humor there for you if you got it.

Anyway… Safari…

I like (love ES6 classes), I build my own render function for my own elements and before you tell me to use a framework, I prefer vanilla and myself than to depend on others code, their bugs and their CDN nightmares or whims. Anyway, in my components they have event handlers that call back into a main function to make fetch() calls, sometimes I make those calls inside the component if they are specialized. It might look like this:

window.setTimeout(async() => {
const o = new thing();
await o.render();
}, 1);
class thing {
render = async () => {
const html = /*html*/`<button id="btn1">Test</button>`;
document.body.innerHTML = html;
const msg = "something here";
document.getElementById("btn1").addEventListener("click", async () => {
await this.#getData(msg);
});
}
#getData = async (msg) => {
const result = await fetch('https://jsonplaceholder.typicode.com/posts/1&#39;);
console.log(msg);
const data = await result.json();
console.log(data);
}
}
view raw safari_issue.js hosted with ❤ by GitHub

Now, this code works in Windows and in every browser, and on Mac in every browser and oddly enough, the above simplified code actually works great in Safari, but embedded a few layers deep in event handlers of other components with async because other fetch calls collected data, etc., etc.… It FAILS every time in Safari and only Safari.

What happens is that “msg” variable that I pass into my fetching function (this.#getData) is always UNDEFINED. And I have tried passing just a raw string “this is a string” and it fails. Maybe it is stack depth and a combination of async and even handler callbacks, or something funky with binding of this when you get into things a few layers. But the bottom line is that it ONLY happens in Safari and the ONLY way I have found to work around this is to remove all the async functions and await calls and put the fetch in a window.setTimeout(), like this:

class thing {
render = () => {
const html = /*html*/`<button id="btn1">Test</button>`;
document.body.innerHTML = html;
const msg = "something here";
document.getElementById("btn1").addEventListener("click", () => {
this.#getData(msg);
});
}
#getData = (msg) => {
window.setTimeout(async () => {
const result = await fetch('https://jsonplaceholder.typicode.com/posts/1&#39;);
console.log(msg);
const data = await result.json();
console.log(data);
}, 1);
}
}
// instantiate
const o = new thing();
o.render();

And I did try an old school Promise here where the window.setTimeout and the async on the Promise did the same thing with the “msg” parameter – undefined.

Anyway, I hope this helps someone else experiencing the same issue. And if you have had this issue before and fixed it in another way, please let me know. If you are aware of this issue and it is documented somewhere that I will not encounter an angry D’Onofrio bartender/lab sidekick, please let me know.

Solving Outlook JS Email Reply Parsing with ES6 Class | Example Included

In working with Send to Trello, Send to Planner and Send to Asana, I found one of the most complicated tasks in Outlook JS is trying to determine where an email reply ends, and the original message begins in order to get the latest response in a Message.Read scenario.

Seems simple enough that you look for the break between the messages, our eyes pick that up fairly easily. I have not tried AI yet, but that is a next step for sure.

However, wanted to share what I have found works in most cases. I created an ES6 class OutlookEmailBodyParser. And you use it like this:

const emailBody = new Promise((resolve, reject) => {
try {
Office.context.mailbox.item.body.getAsync(type, (result) => {
if (result.status === Office.AsyncResultStatus.Succeeded) {
resolve(result.value);
} else {
reject(result.error);
}
});
} catch {
reject("Unable to get email body text.");
}
});
const parser = new OutlookEmailBodyParser(emailBody);
return parser.getLatestResponse();

And here is the class that does all the work:

export default class OutlookEmailBodyParser {
/** @type {String} */
#body = null;
/**
* Creates an instance of the Outlook Email Body Parser
* Next you call:
* – getLatestResponse() to get the most recent message
* @param {String} body
*/
constructor(body) {
this.#body = body;
}
/**
* Returns the latest response
*/
getLatestResponse = () => {
const lines = this.#body.replace("/\r/g", "\n").split("\n");
// any line that starts with a word, a color and a space, like From: , to: , Cc:, Date:
const prologLine = /(^[A-Z]{2,8}:\s)/i;
// any line that ends with a colon
const lineEndsWithColon = /^.+(:)$/;
// covers general patterns of first.last@email.domain
const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z]{2,6}/;
// covers ——-, _______, —–original message—-, _______PREV______
const breakRegex = /^([-_]+(\w{0,15}(\s|\s{0})){0,3}[-_]+)$/;
// looks for a 4 digit number on the line, we grab it and see if it looks reasonable
// meaning in compare is the 4 digit number starting with 19 or 20.
const containsYear = /([/\s][0-9]{4})/;
// looks for a time in formats 4:44, 16:44, 4:44 AM, 4:44 PM
// we then look at capture groups to verify in range
const containsTime = /\s([0-9]{1,2}):([0-9]{2})(\w{2}|\s\w{2}|:[0-9]{2}|.{0})/;
// — START —
var breakOnLine = "";
var fFoundLineBreak = false;
var candidateLines = 0;
var prevLine = "";
for (const line of lines) {
var gmailFoundCount = 0;
var outlookFoundCount = 0;
if (breakRegex.test(line)) {
fFoundLineBreak = true;
candidateLines++;
prevLine = line;
continue;
}
if (emailRegex.test(line)) {
gmailFoundCount++;
outlookFoundCount++;
}
if (prologLine.test(line)) outlookFoundCount++;
if (lineEndsWithColon.test(line)) gmailFoundCount++;
if (containsYear.test(line)) {
const year = containsYear.exec(line);
if (year.length === 2) {
const num = Number.parseInt(year[0].trim());
if (!Number.isNaN(num) && num > 1900 && num < 2100) {
gmailFoundCount++;
outlookFoundCount++;);
}
}
}
if (containsTime.test(line)) {
const time = containsTime.exec(line);
if (time.length >= 3) {
const hour = Number.parseInt(time[1].trim());
const min = Number.parseInt(time[2].trim());
if (!Number.isNaN(hour) && !Number.isNaN(min) && hour >= 0 && hour <= 23 && min >= 0 && min <= 59) {
gmailFoundCount++;
outlookFoundCount++;
}
}
}
if (fFoundLineBreak) outlookFoundCount++;
////////////////////////////////////
// VALIDATE OUTLOOK
////////////////////////////////////
if (candidateLines >= 1 && outlookFoundCount > 2) {
breakOnLine = prevLine;
break;
}
////////////////////////////////////
// VALIDATE GMAIL
////////////////////////////////////
if (gmailFoundCount >= 4) {
breakOnLine = line;
break;
}
if (candidateLines === 1 && gmailFoundCount === 1) {
breakOnLine = prevLine;
break;
}
////////////////////////////////////
// reset
////////////////////////////////////
if (candidateLines === 3) candidateLines = 0;
if (outlookFoundCount >= 2) candidateLines++;
if (gmailFoundCount === 3) candidateLines++;
if (candidateLines === 1) prevLine = line;
fFoundLineBreak = false; // must set here
}
var latestResponse = this.#body;
if (breakOnLine) {
const pos = this.#body.indexOf(breakOnLine);
latestResponse = this.#body.substring(0, pos);
}
// return
return latestResponse;
};
}

Send to Planner v1.04 is Released

I have been absent from my blog for a while as I have been busy working on Kryl Solutions add-ins and Power-Ups. It has been an enjoyable experience to really delve into the world of JavaScript (ES6) and get creative building things.

One of my favorite projects has been Send to Planner. I just released version 1.04 and after 6 months of being in the marketplace, the reception and stats for this add-in are amazing. It has quickly become my fastest growing integration.

With v1.04, I have added checklists. Now, you can do nearly everything with task from Send to Planner: create new tasks in any plan and bucket, add to existing tasks as comments, add start and due dates, update priority, update selected categories, update the task progress, add/remove user assignments, and now, add, remove or update checklist items.

If you use Microsoft Planner for managing your projects, and you deal a lot with incoming email driving or updating your task workflow, you really should check Send to Planner out and let me know what you think.

Send to Planner Add-in Published

Right before the holidays I got my latest solution published to AppSource: “Send to Planner.”

This add-in is based on the “Send to Trello” Add-in and has a very similar look and feel. It was an unexpected side bar in my solutions development. A Send to Trello user contacted me because they also used Planner and were unable to find an effective solution that just did the basic “send an email to Planner” for free. While this has premium features to do much more, like keeping responses to the same email with the original task, the basic functionality will likely meet most casual use.

This was fun to create but also vexing at the same time. I am not sure where I got the idea, that there was an ability to get a front-end access token of the user running the Office add-in (getAccessToken). But turns out this is called an “on behalf of” flow that requires a complex manifest setup and you must send the customer email information to your backend web service to make the call “on behalf of” the user to Graph API. There are several options, but I had to go with the OAUTH flow using displayDialogAsync(). The AppSource (GDRP/Privacy) requirements for a backend data flow was more work and upkeep than I care for (cost and time wise). It is a headache I did not want.

So, I used the MSAL flow to pop an authentication dialog and then call the Graph API from the front-end to write to Planner. I already have users complaining that “it pops up the authentication dialog too much.” This is even though it does not require the entire authentication flow, it just pops up and then goes away.

I try the ssoSilent from the task-pane side and when/if that fails, I pop the dialog and do it there. The issue is because I do not own the frame of the Office task pane my origination domain is not correct. MSAL rejects the Silent SSO attempt. Ergo, it must pop open the dialog every time to get ssoSilent and refresh the token that way. A tad annoying.

I am not sure if I am missing something, but it would be nice to be able to request a front-end token with the proper scopes to do this, or a way to call ssoSilent from a task pane. But for now – this is what it is. Another annoying fact is that the token I get from MSAL, although it is refreshable (with ssoSilent), it lasts only 1 hour before it must be refreshed. So, 99% of the time a user clicks “Send to Planner” they see the dialog flash.

Either way, the add-in is out there and consumable. Please check it out!

Adding an Attachment to a Planner Task

I have recently started working on a new add-in in Office and had reason to create an newly uploaded attachment to a Planner Task item.

OMG!

I pulled out all my hair on this one as I was unable to figure out from the documentation how to do this. Hack, hack, hack — 3days later… well, I hope to save the world this frustration.

Here are the steps:

  1. First, get the file from an upload. Determine the type and convert all the binary in the file to a BASE64 string.
  2. Next, you need to GET the planners GROUP by the planner ID: https://graph.microsoft.com/v1.0/planner/plans/${plannerId}
  3. Then you create the item in the group drive container, where name is the name of the file via a POST (with the based 64 in the body, content-type: text/plain): https://graph.microsoft.com/v1.0/groups/${plan.container.containerId}/drive/items/root:/${name}:/content
  4. This returns a driveItem object and from this you need the driveItem.webUrl, but this is the part that killed me. The documentation tells you to use an encoded URL. WRONG. This fails every single time. After a lot of looking over the documentation and seeing what they were submitting, you actually only replace the “.” and the “:” and white space / /. Here is a function I created in JS:
/**
* Encodes the URL ins the special planner format that is for oif but not quite
* following the encodeURIComponent() specification…
* @param {String} url
* @returns {String}
*/
#encodePlannerExternalReferenceUrl = (url) => {
// Encode specific characters: : . _
const encodedUrl = url.replace(/:/g, "%3A").replace(/\./g, "%2E").replace(/ /g, "%20");
return encodedUrl;
};
  1. Next, GET the task item by ID: https://graph.microsoft.com/v1.0/planner/tasks/${id}
  2. Then GET the task items details by ID: https://graph.microsoft.com/v1.0/planner/tasks/${id}/details
  3. For this next part, you use the “@odata.etag” prop the details and you will create a new entry with a PATCH: https://graph.microsoft.com/v1.0/planner/tasks/${taskId}/details.
  4. For this part you need to create a fetch body like this: { references: ${ref} }, where the ref is defined as:
const ref = {
/** @type {PlannerReference} */
[this.#encodePlannerExternalReferenceUrl(driveItem.webUrl)]: {
"@odata.type": "#microsoft.graph.plannerExternalReference",
alias: name,
type: "Other",
},
};
view raw ref.js hosted with ❤ by GitHub
  1. To make the above call you have to supply the @odata.etag. And this is the NEXT part that messed with my noodle. You do NOT use the whole value returned AND you have to place it in double quotes. Ugh! So here is more code I wrote to help with that:
const updatedTag = eTag.replace('W/"', '"').replace('\\"', "");
view raw etag.js hosted with ❤ by GitHub
  1. Then when you make the call you place the tag in the headers: If-Match. And do not forget to add the Content-type, and to see the returned result the Prefer as well:

The final submission looks like this:

And let me stress this. You must PATCH this, not “patch.” Another thing I found is that GET/get, PUT/put, DELETE/delete, and POST/post all work interchangeably. But if you “patch” you will get some bazaar error about CORS and that path is not supported. And if you hit the service with OPTIONS you will see all the supported methods are returned in CAPS. Oddly, all work with lowercase or upper case, except for PATCH.

So, there you have it. 3 days of my life.

Procure the Board Button in Trello

I was recently posting an issue in the Developer Forum for Trello and found a post I could answer fairly quickly since I knew the answer already. Thought I would share it here on my blog as well.

The question was along the lines of:

How do I show my board button only to members of the board who have paid for it? Or how do I block guests from getting the board button?

Here is the code:

/// <reference path="trello.d.js" />
/** @type {TrelloPowerUp} */
const tpu = window.TrelloPowerUp;
tpu.initialize({
'board-buttons':
/**
* Returns the board button
* @param {TrelloObject} t
* @returns {TrelloBoardButtonOption[]}
*/
async (t) => {
/** @type {TrelloMemberObject} */
const member = await t.member("id");
/** @type {TrelloBoard} */
const board = await t.board("memberships");
/** @type {TrelloMembership} */
const membership = board.memberships.find(o=>o.idMember === member.id);
if(!membership || membership.memberType === "observer") {
t.alert({
message: "Sorry you are only a guest on this board!",
duration: 1,
});
return []; // no board button for you
}
/** @type {TrelloBoardButtonOption} */
const button = {
text: "hello",
icon: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAF2SURBVDhPtZMxT8JQEMfb0pI2xMbA0JWyNXQEJLHO7VrmfgAHTfgMLMTo0FknDU0TNze+gCbOSmSBwU2hgxMlAevd8wV95GG6+Euu73/v/e/aXlPhX8myrIBBUy4iXRmCIDicTqeeqqoHmKdp+lir1YaDweCeGHZx1u/vHTnOpWEYqSiKGWyRQI17juNc9cFDzNvEUay2ms1bkJtCXjTBE0WRCprFc70TTdO4Rb8DPa7rnoL+odfr6bZtP4HkFm0HeJ+xBrQg4WU+n7eSJLFR5wH8dfC3UJMGy+WyDJNGmQvwC4vFooyaNFAUZVUo/Pm5GdBbLBZXqEkD2Bjpuv6BOg/olSRpRNNv2u32NSzcoW0HeG9gJZAnQOx6/cKsVmc03YlZNWfgPacpi+/7rmma7yC5d8azDnhAb2AmNx6PJ77fGWqaqsmyvF8qleB19c9KpfJqWdZdo9E4juP4gdoJ3J8J6Xa7BgzXQr1er1/CMHwjBwyC8AW6vpgYpmCzMQAAAABJRU5ErkJggg==`, // for card front badges only
condition: "always",
callback: (tt) => {
tt.alert({
message: "You are all paid up!",
duration: 1,
})
}
};
// return the button
return [button];
}
});
view raw client.js hosted with ❤ by GitHub

Happy coding!