async _processExistingMessage({existingMessage, messageValues, struct} = {}) {
    const {accountId} = messageValues;
    const db = await LocalDatabaseConnector.forAccount(accountId);

    /**
     * There should never be a reason to update the body of a message
     * already in the database.
     *
     * When we use link/open tracking on Gmail, we optimistically create a
     * Message whose body is stripped of tracking pixels (so you don't
     * self trigger). Since it takes time to delete the old draft on Gmail
     * & restuff, it's possible to sync a message with a non-stripped body
     * (which would cause you to self-trigger)). This prevents this from
     * happening.
     */
    const newMessageWithoutBody = _.clone(messageValues)
    delete newMessageWithoutBody.body;
    await existingMessage.update(newMessageWithoutBody);
    if (messageValues.labels && messageValues.labels.length > 0) {
      await existingMessage.setLabels(messageValues.labels)
    }

    let thread = await existingMessage.getThread({
      include: [{model: db.Folder, as: 'folders'}, {model: db.Label, as: 'labels'}],
    });
    if (!existingMessage.isProcessed) {
      if (!thread) {
        thread = await detectThread({db, messageValues});
        existingMessage.threadId = thread.id;
      } else {
        await thread.updateFromMessages({db, messages: [existingMessage]})
      }
      await this._addReferences(db, existingMessage, thread, messageValues.references);
      const files = await extractFiles({db, messageValues: existingMessage, struct});
      // Don't count inline images (files with contentIds) as attachments
      if (files.some(f => !f.contentId) && !thread.hasAttachments) {
        thread.hasAttachments = true;
        await thread.save();
      }
      await extractContacts({db, messageValues: existingMessage});
      existingMessage.isProcessed = true;
    } else {
      if (!thread) {
        throw new Error(`Existing processed message ${existingMessage.id} doesn't have thread`)
      }
    }

    await existingMessage.save();
    await thread.updateLabelsAndFolders();
    return existingMessage
  }
When we send a message we store an incomplete copy in the local
database while we wait for the sync loop to discover the actually
delivered one. We store this to keep track of our delivered state and
to ensure it's in the sent folder.

It'll have the basic ID, but no thread, labels, etc.

We also get already processed messages because they may have had their
folders or labels changed or had some other property updated with them,
or because we interrupted the sync loop before the message was fully
processed.