Writing to and reading from socket in an IMAP client I implemented the following method to send IMAP commands to server and return the response to caller. Here, I'm not trying to parse the commands. Would like some advice on how I can improve the method (e.g. avoiding unnecessary copying) because I feel like I keep moving data between buffers and strings too much.

The other concern I have is about the size of the response. Right now, I'm returning a string. But this string can be potentially large (example fetch an email with a large email body or 100 email headers that contains subject, date and sender). In such a case, should I allocate response in the free-store and return a pointer to it or something like that? Thank you.

std::string cmail::ImapClient::execute(const std::string &amp;command)
{
    std::stringstream ss;
    ss &lt;&lt; ++commandCounter &lt;&lt; " " &lt;&lt; command;
    std::string cmd = ss.str();
    spdlog::trace("C: " + cmd);
    boost::system::error_code error;
    boost::asio::write(socket, boost::asio::buffer(cmd.append("\r\n")), error);
    if(error)
    {
        spdlog::error("Failed to dispatch command " + cmd + " because " + error.message());
        return ""; 
    }

    boost::asio::streambuf buffer;
    boost::asio::read_until(socket, buffer, '\n', error);
    if(error)
    {
        spdlog::error("Failed to receive response for command " + cmd + " because " + error.message());
        return "";
    }

    ss.str(std::string());
    ss.clear();
    std::istream is(&amp;buffer);
    std::string line;
    while(std::getline(is, line))
    {
       ss &lt;&lt; line &lt;&lt; std::endl; 
    }

    std::string response = ss.str();

    if(spdlog::default_logger()-&gt;level() == spdlog::level::trace)
    {
        std::string s = response;
        s.pop_back();
        spdlog::trace("S: " + s);
    }

    return response;
}


Thank you for your effort and time, helping me.
The code looks fine.  Instead of const std::string &amp;command, you can take std::string_view command to avoid unnecessary allocation when the argument is not readily a std::string (e.g., string literals).  std::ostringstream seems more appropriate than std::stringstream here, since you are not reading from it.  Creating a new stream may also be more readable than reusing the stream.

Prior to C++20, str on string streams always make a copy of the buffer, so there isn't much you can do about it.  Since C++20, you can move the underlying buffer by moving the string stream itself:

// since C++20
std::string response = std::move(ss).str();