private static Message rawMessage(MessageContent content, Chat chat, boolean encrypted) {
    Message smackMessage = new Message();

    // text
    String text = content.getPlainText();
    if (!text.isEmpty()) smackMessage.setBody(content.getPlainText());

    // attachment
    MessageContent.Attachment att = content.getAttachment().orElse(null);
    if (att != null) {
      OutOfBandData oobData =
          new OutOfBandData(att.getURL().toString(), att.getMimeType(), att.getLength(), encrypted);
      smackMessage.addExtension(oobData);

      MessageContent.Preview preview = content.getPreview().orElse(null);
      if (preview != null) {
        String data = EncodingUtils.bytesToBase64(preview.getData());
        BitsOfBinary bob = new BitsOfBinary(preview.getMimeType(), data);
        smackMessage.addExtension(bob);
      }
    }

    // group command
    if (chat instanceof KonGroupChat) {
      KonGroupChat groupChat = (KonGroupChat) chat;
      KonGroupData gid = groupChat.getGroupData();
      MessageContent.GroupCommand groupCommand = content.getGroupCommand().orElse(null);
      smackMessage.addExtension(
          groupCommand != null
              ? ClientUtils.groupCommandToGroupExtension(groupChat, groupCommand)
              : new GroupExtension(gid.id, gid.owner.string()));
    }

    return smackMessage;
  }
  boolean sendMessage(OutMessage message, boolean sendChatState) {
    // check for correct receipt status and reset it
    KonMessage.Status status = message.getStatus();
    assert status == KonMessage.Status.PENDING || status == KonMessage.Status.ERROR;
    message.setStatus(KonMessage.Status.PENDING);

    if (!mClient.isConnected()) {
      LOGGER.info("not sending message(s), not connected");
      return false;
    }

    MessageContent content = message.getContent();
    MessageContent.Attachment att = content.getAttachment().orElse(null);
    if (att != null && !att.hasURL()) {
      LOGGER.warning("attachment not uploaded");
      message.setStatus(KonMessage.Status.ERROR);
      return false;
    }

    boolean encrypted =
        message.getCoderStatus().getEncryption() != Coder.Encryption.NOT
            || message.getCoderStatus().getSigning() != Coder.Signing.NOT;

    Chat chat = message.getChat();

    Message protoMessage = encrypted ? new Message() : rawMessage(content, chat, false);

    protoMessage.setType(Message.Type.chat);
    protoMessage.setStanzaId(message.getXMPPID());
    String threadID = chat.getXMPPID();
    if (!threadID.isEmpty()) protoMessage.setThread(threadID);

    // extensions

    // TODO with group chat? (for muc "NOT RECOMMENDED")
    if (!chat.isGroupChat()) protoMessage.addExtension(new DeliveryReceiptRequest());

    if (sendChatState) protoMessage.addExtension(new ChatStateExtension(ChatState.active));

    if (encrypted) {
      byte[] encryptedData =
          content.isComplex() || chat.isGroupChat()
              ? Coder.encryptStanza(message, rawMessage(content, chat, true).toXML().toString())
                  .orElse(null)
              : Coder.encryptMessage(message).orElse(null);
      // check also for security errors just to be sure
      if (encryptedData == null || !message.getCoderStatus().getErrors().isEmpty()) {
        LOGGER.warning("encryption failed");
        message.setStatus(KonMessage.Status.ERROR);
        mControl.handleSecurityErrors(message);
        return false;
      }
      protoMessage.addExtension(new E2EEncryption(encryptedData));
    }

    // transmission specific
    Transmission[] transmissions = message.getTransmissions();
    ArrayList<Message> sendMessages = new ArrayList<>(transmissions.length);
    for (Transmission transmission : message.getTransmissions()) {
      Message sendMessage = protoMessage.clone();
      JID to = transmission.getJID();
      if (!to.isValid()) {
        LOGGER.warning("invalid JID: " + to);
        return false;
      }
      sendMessage.setTo(to.string());
      sendMessages.add(sendMessage);
    }

    return mClient.sendPackets(sendMessages.toArray(new Message[0]));
  }