Closed Bug 1631580 Opened 4 years ago Closed 2 years ago

API to save composed message to Drafts

Categories

(Thunderbird :: Add-Ons: Extensions API, enhancement)

enhancement
Not set
normal

Tracking

(Not tracked)

RESOLVED MOVED

People

(Reporter: jon, Unassigned)

Details

User Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0

Expected results:

I need some way to catch onBeforeSend events, save the composed message to drafts, then closing the composition window without actually sending the message.

Ideally, I'd like to add a tag or custom header to the message in the same step, but that may be too specific of a feature to be part of the API. In any case, the save function should return a messageId for the saved draft.

Can you describe the broader use case?

My specific use case is the Send Later addon (https://addons.thunderbird.net/en-US/thunderbird/addon/send-later-3/), which schedules messages to send at a particular time by storing them in the account's Drafts folder with custom headers that define the send schedule.

This could also be accomplished in a more generalized way, with something like a browser.messages.create(destination, message) API that places a new message directly into any arbitrary folder.

My ideal workflow would be something like:

popup.js:

const tabs = await browser.tabs.query({ active:true, currentWindow:true });
browser.runtime.sendMessage({ tabID: tabs[0].id, sendAt: (new Date()).toUTCString() });

background.js:

browser.runtime.onMessage.addListener(async (message) => {
  const details = await browser.compose.getComposeDetails(message.tabID);
  const draftsFolder = findDraftsFolder(details.identityId);
  const fullMessage = browser.compose.getFull(message.tabID);
  fullMessage.headers['x-send-later-at'] = message.sendAt;
  browser.messages.create(draftsFolder, fullMessage);
  browser.tabs.remove(tabID);
});

This pattern would enable a variety of use-cases, including storing arbitrary data on an IMAP server, making incremental backups of composed messages, creating templates based on composed messages, etc. This approach isn't 100% perfect for my use case, but it would be much more broadly useful than more specific alternatives.

What are your thoughts on that? I'm of course open to other suggestions on how to accomplish this.

I was trying to understand why onBeforeSend is involved. But this could also work with a composeAction button which triggers your send later functionality (saving compose content as draft, scheduling send, closing composer)?

With other requests/bugs in mind I try to split this into individual API requests.

1. Get account from identity
ComposeDetails includes the identity. If I am not wrong, there is no easy way to get the associated account, but one has to loop over all accounts retrieved via accounts.list() and check for each if it includes the given identity. That could be made easier.

2. Find folder by type.
Finding the drafts folder is already possible. There is no "search" but, from accounts.list() one can get all folders and subfolders. While searching for the account belonging to the given identity (see 1.) the folders array of that account can be obtained at no additional costs. Looping over its subfolders and checking for type "drafts", one can find the drafts folder. That could be made easier.

3. Set headers via setComposeDetails
The ComposeDetails should support a headers object to set headers. I guess we have to work with blacklists, as some headers probably should not be manipulated. There is a similar request here but it needs shaping.

4. Get the current content of the composer as an email, store it and send it later
I am not sure what is plausible, either

const rawMessage = browser.compose.getRaw(tabId);
browser.messages.create(draftsFolder, rawMessage);

or

const messageId = browser.compose.saveAsEmailInFolder(tabId, draftsFolder);

Are there use cases for creating new messages anywhere BUT the drafts folder? Maybe we could just do

const messageId = browser.compose.saveAsDraft(tabId);

and this would automatically save it in the drafts folder belonging to the current identity.

This would then require a functionality to send the draft at a later time from your extension when your scheduler kicks in. I currently have no idea, to which API that function could be added, probably the compose API.

An alternative to this Save-As-Draft-And-Send-Later approach would be to do this:

const details = browser.compose.getComposeDetails(tabId);
<save details in local storage>
tabs.remove(tabid) 
...
//scheduler kicks in
compose.sendMessage(<details from local storage>)

This would of course require the ComposeDetails object to include all aspects of the email.

Sorry for the radio silence. I am swamped with work right now, and I'll get back to this with a more thorough response as soon as I can. In the mean time I'll just say that I agree with your analysis above. I think the best solution for my use case is just

const rawMessage = browser.compose.getRaw(tabId);
browser.messages.create(folder, rawMessage);

This setup would also allow for placing messages in the outbox, and creating new draft messages from scratch with new message ID's. In that case it would also be important to have something like

browser.messages.triggerOutbox();

and maybe

browser.accounts.generateMessageId(accountId, identityId); (or something similar)

The rest of the functionality I can handle within the current API constraints.

After thinking about this a bit more I would add one more thing to that list: an API for executing the pre-send checks on a composition window. Currently, those checks happen in MsgComposeCommands.js, and are executed during GenericSendMessage, but only when the message is actually sending. Send Later needs to manually execute those checks before saving to drafts, since from a user's perspective that step is the send operation.

That API would just be something like:

browser.compose.preSendCheck(tabId)

It would just require moving MsgComposeCommands.js lines 4147 - 4336 into a separate function.

(In reply to Jonathan P-H from comment #0)

User Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0

Expected results:

I need some way to catch onBeforeSend events, save the composed message to drafts, then closing the composition window without actually sending the message.

Ideally, I'd like to add a tag or custom header to the message in the same step, but that may be too specific of a feature to be part of the API. In any case, the save function should return a messageId for the saved draft.

This is now possible in Thunderbird 102.

  1. Bug 1747456 added compose.saveMessage() which saves the current message being composed as a draft and returns the id of the saved message. You may have to abort the send process first and then save the message. IIRC the save cannot be initiated while a send is still ongoing. The onComposeStateChanged listener should tell you once the composer has fully aborted the send and is ready for another send (or save).

  2. Bug 1749198 added compose.setComposeDetails({customHeaders: [{name: "X-SenderMood", value: "Awesome"}]}

  3. Multiple bugs added all email details except encryption state to ComposeDetails.

Status: UNCONFIRMED → RESOLVED
Closed: 2 years ago
Resolution: --- → MOVED

That's awesome. Thank you, John!

Work and life have been preventing me from putting time into Send Later recently, and that probably won't change much throughout the summer. But as soon as I have real time to devote to this, I really look forward to inching that much closer to full mailextension compatibility!!

You need to log in before you can comment on or make changes to this bug.