diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp
index 8c6478b59f..4c034721d1 100644
--- a/assignment-client/src/AssignmentClientMonitor.cpp
+++ b/assignment-client/src/AssignmentClientMonitor.cpp
@@ -195,8 +195,8 @@ void AssignmentClientMonitor::checkSpares() {
             SharedNodePointer childNode = nodeList->nodeWithUUID(aSpareId);
             childNode->activateLocalSocket();
 
-            QByteArray diePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeStopNode);
-            nodeList->writeUnverifiedDatagram(diePacket, childNode);
+            auto diePacket { NLPacket::create(PacketType::StopNode); }
+            nodeList->sendPacket(diePacket, childNode);
         }
     }
 }
@@ -229,8 +229,9 @@ void AssignmentClientMonitor::readPendingDatagrams() {
                         } else {
                             // tell unknown assignment-client child to exit.
                             qDebug() << "asking unknown child to exit.";
-                            QByteArray diePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeStopNode);
-                            nodeList->writeUnverifiedDatagram(diePacket, senderSockAddr);
+
+                            auto diePacket { NL::create(PacketType::StopNode); }
+                            nodeList->sendPacket(diePacket, childNode);
                         }
                     }
                 }
diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index abbf8071f7..055294b9bf 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -509,24 +509,20 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
 
     if (sendData) {
         auto nodeList = DependencyManager::get<NodeList>();
-        int numBytesEnvPacketHeader = nodeList->populatePacketHeader(clientEnvBuffer, PacketTypeAudioEnvironment);
-        char* envDataAt = clientEnvBuffer + numBytesEnvPacketHeader;
+        auto envPacket = NLPacket::create(PacketType::AudioEnvironment);
 
         unsigned char bitset = 0;
         if (hasReverb) {
             setAtBit(bitset, HAS_REVERB_BIT);
         }
 
-        memcpy(envDataAt, &bitset, sizeof(unsigned char));
-        envDataAt += sizeof(unsigned char);
+        envPacket.write(&bitset, sizeof(unsigned char));
 
         if (hasReverb) {
-            memcpy(envDataAt, &reverbTime, sizeof(float));
-            envDataAt += sizeof(float);
-            memcpy(envDataAt, &wetLevel, sizeof(float));
-            envDataAt += sizeof(float);
+            envPacket.write(&reverbTime, sizeof(float));
+            envPacket.write(&wetLevel, sizeof(float));
         }
-        nodeList->writeDatagram(clientEnvBuffer, envDataAt - clientEnvBuffer, node);
+        nodeList->sendPacket(envPacket, node);
     }
 }
 
@@ -712,8 +708,6 @@ void AudioMixer::run() {
     QElapsedTimer timer;
     timer.start();
 
-    char clientMixBuffer[MAX_PACKET_SIZE];
-
     int usecToSleep = AudioConstants::NETWORK_FRAME_USECS;
 
     const int TRAILING_AVERAGE_FRAMES = 100;
@@ -791,8 +785,8 @@ void AudioMixer::run() {
                 // if the stream should be muted, send mute packet
                 if (nodeData->getAvatarAudioStream()
                     && shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness())) {
-                    QByteArray packet = nodeList->byteArrayWithPopulatedHeader(PacketTypeNoisyMute);
-                    nodeList->writeDatagram(packet, node);
+                    auto mutePacket { NLPacket::create(PacketType::NoisyMute); }
+                    nodeList->sendPacket(mutePacket, node);
                 }
 
                 if (node->getType() == NodeType::Agent && node->getActiveSocket()
@@ -800,41 +794,36 @@ void AudioMixer::run() {
 
                     int streamsMixed = prepareMixForListeningNode(node.data());
 
-                    char* mixDataAt;
+                    std::unique_ptr<NLPacket> mixPacket;
+
                     if (streamsMixed > 0) {
-                        // pack header
-                        int numBytesMixPacketHeader = nodeList->populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio);
-                        mixDataAt = clientMixBuffer + numBytesMixPacketHeader;
+                        int mixPacketBytes = sizeof(quint16) + AudioConstants::NETWORK_FRAME_BYTES_STEREO;
+                        mixPacket = NLPacket::create(PacketType::MixedAudio);
 
                         // pack sequence number
                         quint16 sequence = nodeData->getOutgoingSequenceNumber();
-                        memcpy(mixDataAt, &sequence, sizeof(quint16));
-                        mixDataAt  += sizeof(quint16);
+                        mixPacket.write(&sequence, sizeof(quint16));
 
                         // pack mixed audio samples
-                        memcpy(mixDataAt, _mixSamples, AudioConstants::NETWORK_FRAME_BYTES_STEREO);
-                        mixDataAt += AudioConstants::NETWORK_FRAME_BYTES_STEREO;
+                        mixPacket.write(mixSamples, AudioConstants::NETWORK_FRAME_BYTES_STEREO);
                     } else {
-                        // pack header
-                        int numBytesPacketHeader = nodeList->populatePacketHeader(clientMixBuffer, PacketTypeSilentAudioFrame);
-                        mixDataAt = clientMixBuffer + numBytesPacketHeader;
+                        int silentPacketBytes = sizeof(quint16) + sizeof(quint16);
+                        mixPacket = NLPacket::create(PacketType::SilentAudioFrame);
 
                         // pack sequence number
                         quint16 sequence = nodeData->getOutgoingSequenceNumber();
-                        memcpy(mixDataAt, &sequence, sizeof(quint16));
-                        mixDataAt += sizeof(quint16);
+                        mixPacket.write(&sequence, sizeof(quint16));
 
                         // pack number of silent audio samples
                         quint16 numSilentSamples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO;
-                        memcpy(mixDataAt, &numSilentSamples, sizeof(quint16));
-                        mixDataAt += sizeof(quint16);
+                        mixPacket.write(&numSilentSamples, sizeof(quint16));
                     }
 
                     // Send audio environment
                     sendAudioEnvironmentPacket(node);
 
                     // send mixed audio packet
-                    nodeList->writeDatagram(clientMixBuffer, mixDataAt - clientMixBuffer, node);
+                    nodeList->sendPacket(mixPacket, node);
                     nodeData->incrementOutgoingMixedAudioSequenceNumber();
 
                     // send an audio stream stats packet if it's time
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 6879da8c08..05dc54665f 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -111,7 +111,7 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend() {
     QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
     for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) {
         PositionalAudioStream* stream = i.value();
-        
+
         if (stream->popFrames(1, true) > 0) {
             stream->updateLastPopOutputLoudnessAndTrailingLoudness();
         }
@@ -147,53 +147,46 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
     // since audio stream stats packets are sent periodically, this is a good place to remove our dead injected streams.
     removeDeadInjectedStreams();
 
-    char packet[MAX_PACKET_SIZE];
     auto nodeList = DependencyManager::get<NodeList>();
 
-    // The append flag is a boolean value that will be packed right after the header.  The first packet sent 
+    // The append flag is a boolean value that will be packed right after the header.  The first packet sent
     // inside this method will have 0 for this flag, while every subsequent packet will have 1 for this flag.
     // The sole purpose of this flag is so the client can clear its map of injected audio stream stats when
     // it receives a packet with an appendFlag of 0. This prevents the buildup of dead audio stream stats in the client.
     quint8 appendFlag = 0;
 
-    // pack header
-    int numBytesPacketHeader = nodeList->populatePacketHeader(packet, PacketTypeAudioStreamStats);
-    char* headerEndAt = packet + numBytesPacketHeader;
-
-    // calculate how many stream stat structs we can fit in each packet
-    const int numStreamStatsRoomFor = (MAX_PACKET_SIZE - numBytesPacketHeader - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
-
     // pack and send stream stats packets until all audio streams' stats are sent
     int numStreamStatsRemaining = _audioStreams.size();
     QHash<QUuid, PositionalAudioStream*>::ConstIterator audioStreamsIterator = _audioStreams.constBegin();
     while (numStreamStatsRemaining > 0) {
 
-        char* dataAt = headerEndAt;
+        auto statsPacket { NLPacket::create(PacketType::AudioStreamStats); }
 
-        // pack the append flag
-        memcpy(dataAt, &appendFlag, sizeof(quint8));
+        // pack the append flag in this packet
+        statsPacket.write(&appendFlag, sizeof(quint8));
         appendFlag = 1;
-        dataAt += sizeof(quint8);
+
+        int numStreamStatsRoomFor = (statsPacket.size() - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
 
         // calculate and pack the number of stream stats to follow
         quint16 numStreamStatsToPack = std::min(numStreamStatsRemaining, numStreamStatsRoomFor);
-        memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
-        dataAt += sizeof(quint16);
+        statsPacket.write(&numStreamStatsToPack, sizeof(quint16));
 
         // pack the calculated number of stream stats
         for (int i = 0; i < numStreamStatsToPack; i++) {
             PositionalAudioStream* stream = audioStreamsIterator.value();
+
             stream->perSecondCallbackForUpdatingStats();
+
             AudioStreamStats streamStats = stream->getAudioStreamStats();
-            memcpy(dataAt, &streamStats, sizeof(AudioStreamStats));
-            dataAt += sizeof(AudioStreamStats);
+            statsPacket.write(&streamStats, sizeof(AudioStreamStats));
 
             audioStreamsIterator++;
         }
         numStreamStatsRemaining -= numStreamStatsToPack;
 
         // send the current packet
-        nodeList->writeDatagram(packet, dataAt - packet, destinationNode);
+        nodeList->sendPacket(statsPacket, destinationNode);
     }
 }
 
@@ -220,7 +213,7 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() const {
     result["downstream"] = downstreamStats;
 
     AvatarAudioStream* avatarAudioStream = getAvatarAudioStream();
- 
+
     if (avatarAudioStream) {
         QJsonObject upstreamStats;
 
@@ -246,7 +239,7 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() const {
     } else {
         result["upstream"] = "mic unknown";
     }
-    
+
     QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
     QJsonArray injectorArray;
     for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) {
@@ -270,7 +263,7 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() const {
             upstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
             upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
             upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
-            
+
             injectorArray.push_back(upstreamStats);
         }
     }
@@ -304,7 +297,7 @@ void AudioMixerClientData::printAudioStreamStats(const AudioStreamStats& streamS
         streamStats._desiredJitterBufferFrames,
         streamStats._framesAvailableAverage,
         streamStats._framesAvailable);
-    
+
     printf("                 Ringbuffer stats | starves: %u, prev_starve_lasted: %u, frames_dropped: %u, overflows: %u\n",
         streamStats._starveCount,
         streamStats._consecutiveNotMixedCount,
@@ -323,10 +316,10 @@ void AudioMixerClientData::printAudioStreamStats(const AudioStreamStats& streamS
 }
 
 
-PerListenerSourcePairData* AudioMixerClientData::getListenerSourcePairData(const QUuid& sourceUUID) { 
+PerListenerSourcePairData* AudioMixerClientData::getListenerSourcePairData(const QUuid& sourceUUID) {
     if (!_listenerSourcePairData.contains(sourceUUID)) {
         PerListenerSourcePairData* newData = new PerListenerSourcePairData();
         _listenerSourcePairData[sourceUUID] = newData;
     }
-    return _listenerSourcePairData[sourceUUID]; 
+    return _listenerSourcePairData[sourceUUID];
 }
diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp
index 7ef9c42524..af9328cd12 100644
--- a/assignment-client/src/avatars/AvatarMixer.cpp
+++ b/assignment-client/src/avatars/AvatarMixer.cpp
@@ -30,7 +30,7 @@
 
 const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
 
-const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 60; 
+const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 60;
 const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000;
 
 AvatarMixer::AvatarMixer(const QByteArray& packet) :
@@ -63,73 +63,70 @@ const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f;
 //    1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present
 //       if the avatar is not in view or in the keyhole.
 void AvatarMixer::broadcastAvatarData() {
-    
+
     int idleTime = QDateTime::currentMSecsSinceEpoch() - _lastFrameTimestamp;
-    
+
     ++_numStatFrames;
-    
+
     const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f;
     const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f;
-    
+
     const float RATIO_BACK_OFF = 0.02f;
-    
+
     const int TRAILING_AVERAGE_FRAMES = 100;
     int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
-    
+
     const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
     const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
-   
+
     // NOTE: The following code calculates the _performanceThrottlingRatio based on how much the avatar-mixer was
     // able to sleep. This will eventually be used to ask for an additional avatar-mixer to help out. Currently the value
     // is unused as it is assumed this should not be hit before the avatar-mixer hits the desired bandwidth limit per client.
     // It is reported in the domain-server stats for the avatar-mixer.
-     
+
     _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
         + (idleTime * CURRENT_FRAME_RATIO / (float) AVATAR_DATA_SEND_INTERVAL_MSECS);
-    
+
     float lastCutoffRatio = _performanceThrottlingRatio;
     bool hasRatioChanged = false;
-    
+
     if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
         if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
             // we're struggling - change our performance throttling ratio
             _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio));
-            
+
             qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
                 << lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
             hasRatioChanged = true;
         } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) {
             // we've recovered and can back off the performance throttling
             _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF;
-            
+
             if (_performanceThrottlingRatio < 0) {
                 _performanceThrottlingRatio = 0;
             }
-            
+
             qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
                 << lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
             hasRatioChanged = true;
         }
-        
+
         if (hasRatioChanged) {
             framesSinceCutoffEvent = 0;
         }
     }
-    
+
     if (!hasRatioChanged) {
         ++framesSinceCutoffEvent;
     }
-    
-    static QByteArray mixedAvatarByteArray;
-    
+
     auto nodeList = DependencyManager::get<NodeList>();
-    int numPacketHeaderBytes = nodeList->populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
-     
-    // setup for distributed random floating point values 
+
+    // setup for distributed random floating point values
     std::random_device randomDevice;
     std::mt19937 generator(randomDevice());
     std::uniform_real_distribution<float> distribution;
-    
+
     nodeList->eachMatchingNode(
         [&](const SharedNodePointer& node)->bool {
             if (!node->getLinkedData()) {
@@ -150,25 +147,22 @@ void AvatarMixer::broadcastAvatarData() {
                 return;
             }
             ++_sumListeners;
-            
-            // reset packet pointers for this node
-            mixedAvatarByteArray.resize(numPacketHeaderBytes);
 
             AvatarData& avatar = nodeData->getAvatar();
             glm::vec3 myPosition = avatar.getPosition();
 
             // reset the internal state for correct random number distribution
             distribution.reset();
-            
+
             // reset the max distance for this frame
             float maxAvatarDistanceThisFrame = 0.0f;
-            
+
             // reset the number of sent avatars
             nodeData->resetNumAvatarsSentLastFrame();
 
             // keep a counter of the number of considered avatars
             int numOtherAvatars = 0;
-            
+
             // keep track of outbound data rate specifically for avatar data
             int numAvatarDataBytes = 0;
 
@@ -185,7 +179,7 @@ void AvatarMixer::broadcastAvatarData() {
             // bandwidth to this node. We do this once a second, which is also the window for
             // the bandwidth reported by node->getOutboundBandwidth();
             if (nodeData->getNumFramesSinceFRDAdjustment() > AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) {
-                
+
                 const float FRD_ADJUSTMENT_ACCEPTABLE_RATIO = 0.8f;
                 const float HYSTERISIS_GAP = (1 - FRD_ADJUSTMENT_ACCEPTABLE_RATIO);
                 const float HYSTERISIS_MIDDLE_PERCENTAGE =  (1 - (HYSTERISIS_GAP * 0.5f));
@@ -195,22 +189,22 @@ void AvatarMixer::broadcastAvatarData() {
 
                 if (avatarDataRateLastSecond > _maxKbpsPerNode) {
 
-                    // is the FRD greater than the farthest avatar? 
+                    // is the FRD greater than the farthest avatar?
                     // if so, before we calculate anything, set it to that distance
                     currentFullRateDistance = std::min(currentFullRateDistance, nodeData->getMaxAvatarDistance());
 
                     // we're adjusting the full rate distance to target a bandwidth in the middle
                     // of the hysterisis gap
-                    currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond; 
-                    
+                    currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
+
                     nodeData->setFullRateDistance(currentFullRateDistance);
                     nodeData->resetNumFramesSinceFRDAdjustment();
-                } else if (currentFullRateDistance < nodeData->getMaxAvatarDistance() 
+                } else if (currentFullRateDistance < nodeData->getMaxAvatarDistance()
                            && avatarDataRateLastSecond < _maxKbpsPerNode * FRD_ADJUSTMENT_ACCEPTABLE_RATIO) {
-                    // we are constrained AND we've recovered to below the acceptable ratio 
+                    // we are constrained AND we've recovered to below the acceptable ratio
                     // lets adjust the full rate distance to target a bandwidth in the middle of the hyterisis gap
                     currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
-                     
+
                     nodeData->setFullRateDistance(currentFullRateDistance);
                     nodeData->resetNumFramesSinceFRDAdjustment();
                 }
@@ -218,6 +212,9 @@ void AvatarMixer::broadcastAvatarData() {
                 nodeData->incrementNumFramesSinceFRDAdjustment();
             }
 
+            // setup a PacketList for the avatarPackets
+            PacketList avatarPacketList(PacketType::AvatarData);
+
             // this is an AGENT we have received head data from
             // send back a packet with other active node data to this node
             nodeList->eachMatchingNode(
@@ -233,16 +230,16 @@ void AvatarMixer::broadcastAvatarData() {
                 },
                 [&](const SharedNodePointer& otherNode) {
                     ++numOtherAvatars;
-                    
+
                     AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
                     MutexTryLocker lock(otherNodeData->getMutex());
                     if (!lock.isLocked()) {
                         return;
                     }
-                    
+
                     AvatarData& otherAvatar = otherNodeData->getAvatar();
                     //  Decide whether to send this avatar's data based on it's distance from us
-                    
+
                     //  The full rate distance is the distance at which EVERY update will be sent for this avatar
                     //  at twice the full rate distance, there will be a 50% chance of sending this avatar's update
                     glm::vec3 otherPosition = otherAvatar.getPosition();
@@ -251,24 +248,24 @@ void AvatarMixer::broadcastAvatarData() {
                     // potentially update the max full rate distance for this frame
                     maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar);
 
-                    if (distanceToAvatar != 0.0f 
+                    if (distanceToAvatar != 0.0f
                         && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) {
                         return;
                     }
 
                     PacketSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID());
-                    PacketSequenceNumber lastSeqFromSender = otherNode->getLastSequenceNumberForPacketType(PacketTypeAvatarData);
+                    PacketSequenceNumber lastSeqFromSender = otherNode->getLastSequenceNumberForPacketType(PacketType::AvatarData);
 
                     if (lastSeqToReceiver > lastSeqFromSender) {
                         // Did we somehow get out of order packets from the sender?
                         // We don't expect this to happen - in RELEASE we add this to a trackable stat
                         // and in DEBUG we crash on the assert
-                        
+
                         otherNodeData->incrementNumOutOfOrderSends();
 
                         assert(false);
                     }
-                    
+
                     // make sure we haven't already sent this data from this sender to this receiver
                     // or that somehow we haven't sent
                     if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
@@ -277,76 +274,70 @@ void AvatarMixer::broadcastAvatarData() {
                     } else if (lastSeqFromSender - lastSeqToReceiver > 1) {
                         // this is a skip - we still send the packet but capture the presence of the skip so we see it happening
                         ++numAvatarsWithSkippedFrames;
-                    } 
-                    
+                    }
+
                     // we're going to send this avatar
-                    
+
                     // increment the number of avatars sent to this reciever
                     nodeData->incrementNumAvatarsSentLastFrame();
-                    
+
                     // set the last sent sequence number for this sender on the receiver
-                    nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), 
-                        otherNode->getLastSequenceNumberForPacketType(PacketTypeAvatarData));
+                    nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
+                        otherNode->getLastSequenceNumberForPacketType(PacketType::AvatarData));
 
-                    QByteArray avatarByteArray;
-                    avatarByteArray.append(otherNode->getUUID().toRfc4122());
-                    avatarByteArray.append(otherAvatar.toByteArray());
-                    
-                    if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) {
-                        nodeList->writeDatagram(mixedAvatarByteArray, node);
+                    // start a new segment in the PacketList for this avatar
+                    avatarPacketList.startSegment();
+
+                    numAvatarDataBytes += avatarPacketList.write(otherNode->getUUID().toRfc4122());
+                    numAvatarDataBytes += avatarPacketList.write(otherAvatar.toByteArray());
+
+                    avatarPacketList.endSegment();
 
-                        numAvatarDataBytes += mixedAvatarByteArray.size();
-                            
-                        // reset the packet
-                        mixedAvatarByteArray.resize(numPacketHeaderBytes);
-                    }
-                        
-                    // copy the avatar into the mixedAvatarByteArray packet
-                    mixedAvatarByteArray.append(avatarByteArray);
-                        
                     // if the receiving avatar has just connected make sure we send out the mesh and billboard
                     // for this avatar (assuming they exist)
                     bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets();
-                        
+
                     // we will also force a send of billboard or identity packet
                     // if either has changed in the last frame
-                        
+
                     if (otherNodeData->getBillboardChangeTimestamp() > 0
                         && (forceSend
                             || otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
                             || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
-                        QByteArray billboardPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
-                        billboardPacket.append(otherNode->getUUID().toRfc4122());
-                        billboardPacket.append(otherNodeData->getAvatar().getBillboard());
 
-                        nodeList->writeDatagram(billboardPacket, node);
-                            
+                        auto billboardPacket { NLPacket::create(PacketType::AvatarBillboard); }
+                        billboardPacket.write(otherNode->getUUID().toRfc4122());
+                        billboardPacket.write(otherNodeData->getAvatar().getBillboard());
+
+                        nodeList->sendPacket(billboardPacket, node);
+
                         ++_sumBillboardPackets;
                     }
-                        
+
                     if (otherNodeData->getIdentityChangeTimestamp() > 0
                         && (forceSend
                             || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
                             || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
-                                
-                        QByteArray identityPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
-                            
+
+                        auto identityPacket { NLPacket::create(PacketType::AvatarIdentity); }
+
                         QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
                         individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
-                        identityPacket.append(individualData);
-                        
-                        nodeList->writeDatagram(identityPacket, node);
-                                
+
+                        identityPacket.write(individualData);
+
+                        nodeList->sendPacket(identityPacket, node);
+
                         ++_sumIdentityPackets;
                     }
             });
-            
-            // send the last packet
-            nodeList->writeDatagram(mixedAvatarByteArray, node);
-            
+
+            // send the avatar data PacketList
+            nodeList->sendPacketList(avatarPacketList, node);
+
             // record the bytes sent for other avatar data in the AvatarMixerClientData
-            nodeData->recordSentAvatarData(numAvatarDataBytes + mixedAvatarByteArray.size());
-            
+            nodeData->recordSentAvatarData(numAvatarDataBytes);
+
             // record the number of avatars held back this frame
             nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
             nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
@@ -359,7 +350,7 @@ void AvatarMixer::broadcastAvatarData() {
             }
         }
     );
-    
+
     _lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
 }
 
@@ -370,9 +361,9 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
 
         // this was an avatar we were sending to other people
         // send a kill packet for it to our other nodes
-        QByteArray killPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
-        killPacket += killedNode->getUUID().toRfc4122();
-        
+        auto killPacket { NLPacket::create(PacketType::KillAvatar); }
+        killPacket.write(killedNode->getUUID().toRfc4122());
+
         nodeList->broadcastToNodes(killPacket, NodeSet() << NodeType::Agent);
 
         // we also want to remove sequence number data for this avatar on our other avatars
@@ -402,9 +393,9 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
 void AvatarMixer::readPendingDatagrams() {
     QByteArray receivedPacket;
     HifiSockAddr senderSockAddr;
-    
+
     auto nodeList = DependencyManager::get<NodeList>();
-    
+
     while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
         if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
             switch (packetTypeForPacket(receivedPacket)) {
@@ -413,14 +404,14 @@ void AvatarMixer::readPendingDatagrams() {
                     break;
                 }
                 case PacketTypeAvatarIdentity: {
-                    
+
                     // check if we have a matching node in our list
                     SharedNodePointer avatarNode = nodeList->sendingNodeForPacket(receivedPacket);
-                    
+
                     if (avatarNode && avatarNode->getLinkedData()) {
                         AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
                         AvatarData& avatar = nodeData->getAvatar();
-                        
+
                         // parse the identity packet and update the change timestamp if appropriate
                         if (avatar.hasIdentityChangedAfterParsing(receivedPacket)) {
                             QMutexLocker nodeDataLocker(&nodeData->getMutex());
@@ -430,20 +421,20 @@ void AvatarMixer::readPendingDatagrams() {
                     break;
                 }
                 case PacketTypeAvatarBillboard: {
-                    
+
                     // check if we have a matching node in our list
                     SharedNodePointer avatarNode = nodeList->sendingNodeForPacket(receivedPacket);
-                    
+
                     if (avatarNode && avatarNode->getLinkedData()) {
                         AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
                         AvatarData& avatar = nodeData->getAvatar();
-                        
+
                         // parse the billboard packet and update the change timestamp if appropriate
                         if (avatar.hasBillboardChangedAfterParsing(receivedPacket)) {
                             QMutexLocker nodeDataLocker(&nodeData->getMutex());
                             nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
                         }
-                        
+
                     }
                     break;
                 }
@@ -463,15 +454,15 @@ void AvatarMixer::readPendingDatagrams() {
 void AvatarMixer::sendStatsPacket() {
     QJsonObject statsObject;
     statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames;
-    
+
     statsObject["average_billboard_packets_per_frame"] = (float) _sumBillboardPackets / (float) _numStatFrames;
     statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames;
-    
+
     statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100;
     statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio;
 
     QJsonObject avatarsObject;
-    
+
     auto nodeList = DependencyManager::get<NodeList>();
     // add stats for each listerner
     nodeList->eachNode([&](const SharedNodePointer& node) {
@@ -482,7 +473,7 @@ void AvatarMixer::sendStatsPacket() {
         // add the key to ask the domain-server for a username replacement, if it has it
         avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
         avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth();
-        
+
         AvatarMixerClientData* clientData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
         if (clientData) {
             MutexTryLocker lock(clientData->getMutex());
@@ -490,9 +481,9 @@ void AvatarMixer::sendStatsPacket() {
                 clientData->loadJSONStats(avatarStats);
 
                 // add the diff between the full outbound bandwidth and the measured bandwidth for AvatarData send only
-                avatarStats["delta_full_vs_avatar_data_kbps"] = 
+                avatarStats["delta_full_vs_avatar_data_kbps"] =
                     avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY].toDouble() - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble();
-            } 
+            }
         }
 
         avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats;
@@ -500,7 +491,7 @@ void AvatarMixer::sendStatsPacket() {
 
     statsObject["avatars"] = avatarsObject;
     ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
-     
+
     _sumListeners = 0;
     _sumBillboardPackets = 0;
     _sumIdentityPackets = 0;
@@ -509,41 +500,41 @@ void AvatarMixer::sendStatsPacket() {
 
 void AvatarMixer::run() {
     ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
-    
+
     auto nodeList = DependencyManager::get<NodeList>();
     nodeList->addNodeTypeToInterestSet(NodeType::Agent);
-    
+
     nodeList->linkedDataCreateCallback = [] (Node* node) {
         node->setLinkedData(new AvatarMixerClientData());
     };
-    
+
     // setup the timer that will be fired on the broadcast thread
     _broadcastTimer = new QTimer;
     _broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS);
     _broadcastTimer->moveToThread(&_broadcastThread);
-    
+
     // connect appropriate signals and slots
     connect(_broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection);
     connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start()));
 
     // wait until we have the domain-server settings, otherwise we bail
     DomainHandler& domainHandler = nodeList->getDomainHandler();
-    
+
     qDebug() << "Waiting for domain settings from domain-server.";
-    
+
     // block until we get the settingsRequestComplete signal
     QEventLoop loop;
     connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
     connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);
     domainHandler.requestDomainSettings();
     loop.exec();
-    
+
     if (domainHandler.getSettingsObject().isEmpty()) {
         qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
         setFinished(true);
         return;
     }
-    
+
     // parse the settings to pull out the values we need
     parseDomainServerSettings(domainHandler.getSettingsObject());
 
@@ -554,13 +545,13 @@ void AvatarMixer::run() {
 void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
     const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer";
     const QString NODE_SEND_BANDWIDTH_KEY = "max_node_send_bandwidth";
-    
+
     const float DEFAULT_NODE_SEND_BANDWIDTH = 1.0f;
     QJsonValue nodeBandwidthValue = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject()[NODE_SEND_BANDWIDTH_KEY];
     if (!nodeBandwidthValue.isDouble()) {
         qDebug() << NODE_SEND_BANDWIDTH_KEY << "is not a double - will continue with default value";
-    } 
+    }
 
     _maxKbpsPerNode = nodeBandwidthValue.toDouble(DEFAULT_NODE_SEND_BANDWIDTH) * KILO_PER_MEGA;
-    qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps."; 
+    qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps.";
 }
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 35be97d610..63e5f5b1eb 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -573,33 +573,34 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
     QDataStream packetStream(packet);
     packetStream.skipRawData(numBytesForPacketHeader(packet));
 
+    QUuid connectUUID;
+    packetStream >> connectUUID;
+
     parseNodeDataFromByteArray(packetStream, nodeType, publicSockAddr, localSockAddr, senderSockAddr);
 
-    QUuid packetUUID = uuidFromPacketHeader(packet);
-
     // check if this connect request matches an assignment in the queue
-    bool isAssignment = _pendingAssignedNodes.contains(packetUUID);
+    bool isAssignment = _pendingAssignedNodes.contains(connectUUID);
     SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
     PendingAssignedNodeData* pendingAssigneeData = NULL;
 
     if (isAssignment) {
-        pendingAssigneeData = _pendingAssignedNodes.value(packetUUID);
+        pendingAssigneeData = _pendingAssignedNodes.value(connectUUID);
 
         if (pendingAssigneeData) {
             matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType);
 
             if (matchingQueuedAssignment) {
-                qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID)
+                qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(connectUUID)
                     << "matches unfulfilled assignment"
                     << uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID());
 
                 // remove this unique assignment deployment from the hash of pending assigned nodes
                 // cleanup of the PendingAssignedNodeData happens below after the node has been added to the LimitedNodeList
-                _pendingAssignedNodes.remove(packetUUID);
+                _pendingAssignedNodes.remove(connectUUID);
             } else {
                 // this is a node connecting to fulfill an assignment that doesn't exist
                 // don't reply back to them so they cycle back and re-request an assignment
-                qDebug() << "No match for assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID);
+                qDebug() << "No match for assignment deployed with" << uuidStringWithoutCurlyBraces(connectUUID);
                 return;
             }
         }
@@ -621,9 +622,8 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
         QByteArray utfString = reason.toUtf8();
         int payloadSize = utfString.size();
 
-        auto connectionDeniedPacket = NodeListPacket::make(PacketType::DomainConnectionDenied, payloadSize);
-
-        memcpy(connectionDeniedPacket.payload().data(), utfString.data(), utfString.size());
+        auto connectionDeniedPacket = NLPacket::make(PacketType::DomainConnectionDenied, payloadSize);
+        connectionDeniedPacket.write(utfString);
 
         // tell client it has been refused.
         limitedNodeList->sendPacket(std::move(connectionDeniedPacket, senderSockAddr);
@@ -638,18 +638,18 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
         QUuid nodeUUID;
 
         HifiSockAddr discoveredSocket = senderSockAddr;
-        SharedNetworkPeer connectedPeer = _icePeers.value(packetUUID);
+        SharedNetworkPeer connectedPeer = _icePeers.value(connectUUID);
 
         if (connectedPeer) {
             //  this user negotiated a connection with us via ICE, so re-use their ICE client ID
-            nodeUUID = packetUUID;
+            nodeUUID = connectUUID;
 
             if (connectedPeer->getActiveSocket()) {
                 // set their discovered socket to whatever the activated socket on the network peer object was
                 discoveredSocket = *connectedPeer->getActiveSocket();
             }
         } else {
-            // we got a packetUUID we didn't recognize, just add the node
+            // we got a connectUUID we didn't recognize, just add the node with a new UUID
             nodeUUID = QUuid::createUuid();
         }
 
diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp
index 36b0345d34..96bca798e3 100644
--- a/libraries/audio/src/AudioInjector.cpp
+++ b/libraries/audio/src/AudioInjector.cpp
@@ -153,54 +153,56 @@ void AudioInjector::injectToMixer() {
     // make sure we actually have samples downloaded to inject
     if (_audioData.size()) {
 
+        auto audioPacket { NLPacket::create(PacketType::InjectAudio); }
+
         // setup the packet for injected audio
-        QByteArray injectAudioPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
-        QDataStream packetStream(&injectAudioPacket, QIODevice::Append);
+        QDataStream audioPacketStream(&audioPacket);
 
         // pack some placeholder sequence number for now
-        int numPreSequenceNumberBytes = injectAudioPacket.size();
-        packetStream << (quint16)0;
+        audioPacketStream << (quint16) 0;
 
         // pack stream identifier (a generated UUID)
-        packetStream << QUuid::createUuid();
+        audioPacketStream << QUuid::createUuid();
 
         // pack the stereo/mono type of the stream
-        packetStream << _options.stereo;
+        audioPacketStream << _options.stereo;
 
         // pack the flag for loopback
         uchar loopbackFlag = (uchar) true;
-        packetStream << loopbackFlag;
+        audioPacketStream << loopbackFlag;
 
         // pack the position for injected audio
-        int positionOptionOffset = injectAudioPacket.size();
-        packetStream.writeRawData(reinterpret_cast<const char*>(&_options.position),
+        int positionOptionOffset = audioPacket.pos();
+        audioPacketStream.writeRawData(reinterpret_cast<const char*>(&_options.position),
                                   sizeof(_options.position));
 
         // pack our orientation for injected audio
-        int orientationOptionOffset = injectAudioPacket.size();
-        packetStream.writeRawData(reinterpret_cast<const char*>(&_options.orientation),
+        int orientationOptionOffset = audioPacket.pos();
+        audioPacketStream.writeRawData(reinterpret_cast<const char*>(&_options.orientation),
                                   sizeof(_options.orientation));
 
         // pack zero for radius
         float radius = 0;
-        packetStream << radius;
+        audioPacketStream << radius;
 
         // pack 255 for attenuation byte
-        int volumeOptionOffset = injectAudioPacket.size();
+        int volumeOptionOffset = audioPacket.pos();
         quint8 volume = MAX_INJECTOR_VOLUME * _options.volume;
-        packetStream << volume;
+        audioPacketStream << volume;
 
-        packetStream << _options.ignorePenumbra;
+        audioPacketStream << _options.ignorePenumbra;
+
+        int audioDataOffset = audioPacket.pos();
 
         QElapsedTimer timer;
         timer.start();
         int nextFrame = 0;
 
-        int numPreAudioDataBytes = injectAudioPacket.size();
         bool shouldLoop = _options.loop;
 
         // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
         quint16 outgoingInjectedAudioSequenceNumber = 0;
+
         while (_currentSendPosition < _audioData.size() && !_shouldStop) {
 
             int bytesToCopy = std::min(((_options.stereo) ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL,
@@ -214,31 +216,29 @@ void AudioInjector::injectToMixer() {
             }
             _loudness /= (float)(bytesToCopy / sizeof(int16_t));
 
-            memcpy(injectAudioPacket.data() + positionOptionOffset,
-                   &_options.position,
-                   sizeof(_options.position));
-            memcpy(injectAudioPacket.data() + orientationOptionOffset,
-                   &_options.orientation,
-                   sizeof(_options.orientation));
-            volume = MAX_INJECTOR_VOLUME * _options.volume;
-            memcpy(injectAudioPacket.data() + volumeOptionOffset, &volume, sizeof(volume));
+            audioPacket.seek(positionOptionOffset);
+            audioPacket.write(&_options.position, sizeof(_options.position));
 
-            // resize the QByteArray to the right size
-            injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
+            audioPacket.seek(orientationOptionOffset);
+            audioPacket.write(&_options.orientation, sizeof(_options.orientation));
+
+            volume = MAX_INJECTOR_VOLUME * _options.volume;
+            audioPacket.seek(volumeOptionOffset);
+            audioPacket.write(&volume, sizeof(volume));
+
+            audioPacket.seek(audioDataOffset);
 
             // pack the sequence number
-            memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes,
-                   &outgoingInjectedAudioSequenceNumber, sizeof(quint16));
+            audioPacket.write(&outgoingInjectedAudioSequenceNumber, sizeof(quint16));
 
             // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
-            memcpy(injectAudioPacket.data() + numPreAudioDataBytes,
-                   _audioData.data() + _currentSendPosition, bytesToCopy);
+            audioPacket.write(_audioData.data() + _currentSendPosition, bytesToCopy);
 
             // grab our audio mixer from the NodeList, if it exists
             SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
 
             // send off this audio packet
-            nodeList->writeDatagram(injectAudioPacket, audioMixer);
+            nodeList->sendUnreliablePacket(audioPacket, audioMixer);
             outgoingInjectedAudioSequenceNumber++;
 
             _currentSendPosition += bytesToCopy;
diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp
index 4eaa61b5e3..235427d705 100644
--- a/libraries/networking/src/LimitedNodeList.cpp
+++ b/libraries/networking/src/LimitedNodeList.cpp
@@ -508,22 +508,22 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
     }
 }
 
-unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) {
-    unsigned n = 0;
+// unsigned LimitedNodeList::broadcastToNodes(PacketList& packetList, const NodeSet& destinationNodeTypes) {
+    // unsigned n = 0;
+    //
+    // eachNode([&](const SharedNodePointer& node){
+    //     if (destinationNodeTypes.contains(node->getType())) {
+    //         writeDatagram(packet, node);
+    //         ++n;
+    //     }
+    // });
+    //
+    // return n;
+// }
 
-    eachNode([&](const SharedNodePointer& node){
-        if (destinationNodeTypes.contains(node->getType())) {
-            writeDatagram(packet, node);
-            ++n;
-        }
-    });
-
-    return n;
-}
-
-NodeListPacket&& LimitedNodeList::constructPingPacket(PingType_t pingType) {
+NLPacket&& LimitedNodeList::constructPingPacket(PingType_t pingType) {
     int packetSize = sizeof(PingType_t) + sizeof(quint64);
-    NodeListPacket pingPacket = NodeListPacket::create(PacketType::Ping, packetSize);
+    auto pingPacket { NLPacket::create(PacketType::Ping, packetSize); }
 
     QDataStream packetStream(&pingPacket.payload(), QIODevice::Append);
 
@@ -533,7 +533,7 @@ NodeListPacket&& LimitedNodeList::constructPingPacket(PingType_t pingType) {
     return pingPacket;
 }
 
-NodeListPacket&& LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket) {
+NLPacket&& LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket) {
     QDataStream pingPacketStream(pingPacket);
     pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket));
 
@@ -542,41 +542,41 @@ NodeListPacket&& LimitedNodeList::constructPingReplyPacket(const QByteArray& pin
 
     quint64 timeFromOriginalPing;
     pingPacketStream >> timeFromOriginalPing;
-    
+
     int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(quint64);
-    
-    NodeListPacket replyPacket = NodeListPacket::create(PacketType::Ping, packetSize);
-    
+
+    auto replyPacket { NLPacket::create(PacketType::Ping, packetSize); }
+
     QDataStream packetStream(&replyPacket, QIODevice::Append);
     packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow();
 
     return replyPacket;
 }
 
-NodeListPacket&& constructICEPingPacket(PingType_t pingType, const QUuid& iceID) {
+NLPacket&& constructICEPingPacket(PingType_t pingType, const QUuid& iceID) {
     int packetSize = NUM_BYTES_RFC4122_UUID + sizeof(PingType_t);
-    
-    NodeListPacket icePingPacket = NodeListPacket::create(PacketType::ICEPing, packetSize);
-    
+
+    auto icePingPacket { NLPacket::create(PacketType::ICEPing, packetSize); }
+
     icePingPacket.payload().replace(0, NUM_BYTES_RFC4122_UUID, iceID.toRfc4122().data());
     memcpy(icePingPacket.payload() + NUM_BYTES_RFC4122_UUID, &pingType, sizeof(PingType_t));
-    
+
     return icePingPacket;
 }
 
-NodeListPacket&& constructICEPingReplyPacket(const QByteArray& pingPacket, const QUuid& iceID) {
+NLPacket&& constructICEPingReplyPacket(const QByteArray& pingPacket, const QUuid& iceID) {
     // pull out the ping type so we can reply back with that
     PingType_t pingType;
-    
+
     memcpy(&pingType, pingPacket.data() + NUM_BYTES_RFC4122_UUID, sizeof(PingType_t));
-    
+
     int packetSize = NUM_BYTES_RFC4122_UUID + sizeof(PingType_t);
-    NodeListPacket icePingReplyPacket = NodeListPacket::create(PacketType::ICEPingReply, packetSize);
-    
+    auto icePingReplyPacket { NLPacket::create(PacketType::ICEPingReply, packetSize); }
+
     // pack the ICE ID and then the ping type
     memcpy(icePingReplyPacket.payload(), iceID.toRfc4122().data(), NUM_BYTES_RFC4122_UUID);
     memcpy(icePingReplyPacket.payload() + NUM_BYTES_RFC4122_UUID, &pingType, sizeof(PingType_t));
-    
+
     return icePingReplyPacket;
 }
 
diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h
index 785933120f..b1905fd593 100644
--- a/libraries/networking/src/LimitedNodeList.h
+++ b/libraries/networking/src/LimitedNodeList.h
@@ -141,6 +141,14 @@ public:
 //
 //    qint64 writeUnverifiedDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode,
 //                         const HifiSockAddr& overridenSockAddr = HifiSockAddr());
+//
+
+    qint64 sendUnreliablePacket(NLPacket& packet, const SharedNodePointer& destinationNode) {};
+    qint64 sendUnreliablePacket(NLPacket& packet, const HifiSockAddr& sockAddr) {};
+    qint64 sendPacket(NLPacket&& packet, const SharedNodePointer& destinationNode) {};
+    qint64 sendPacket(NLPacket&& packet, const HifiSockAddr& sockAddr) {};
+    qint64 sendPacketList(PacketList& packetList, const SharedNodePointer& destinationNode) {};
+    qint64 sendPacketList(PacketList& packetList, const HifiSockAddr& sockAddr) {};
 
     void (*linkedDataCreateCallback)(Node *);
 
@@ -165,17 +173,17 @@ public:
     int updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray& packet);
     int findNodeAndUpdateWithDataFromPacket(const QByteArray& packet);
 
-    unsigned broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes);
+    unsigned broadcastToNodes(PacketList& packetList, const NodeSet& destinationNodeTypes) {};
     SharedNodePointer soloNodeOfType(char nodeType);
 
     void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
     void resetPacketStats();
 
-    NodeListPacket&& constructPingPacket(PingType_t pingType = PingType::Agnostic);
-    NodeListPacket&& constructPingReplyPacket(const QByteArray& pingPacket);
-    
-    NodeListPacket&& constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
-    NodeListPacket&& constructICEPingReplyPacket(const QByteArray& pingPacket, const QUuid& iceID);
+    NLPacket&& constructPingPacket(PingType_t pingType = PingType::Agnostic);
+    NLPacket&& constructPingReplyPacket(const QByteArray& pingPacket);
+
+    NLPacket&& constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
+    NLPacket&& constructICEPingReplyPacket(const QByteArray& pingPacket, const QUuid& iceID);
 
     virtual bool processSTUNResponse(const QByteArray& packet);
 
diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index 11f04e188f..dc21826f2c 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -310,7 +310,7 @@ void NodeList::sendDomainServerCheckIn() {
         bool isUsingDTLS = false;
 
         PacketType::Value domainPacketType = !_domainHandler.isConnected()
-            ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
+            ? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
 
         if (!_domainHandler.isConnected()) {
             qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname();
@@ -329,24 +329,26 @@ void NodeList::sendDomainServerCheckIn() {
 
         }
 
-        // construct the DS check in packet
-        QUuid packetUUID = _sessionUUID;
+        auto domainPacket { NLPacket::create(domainPacketType); }
+        QDataStream packetStream(&domainPacket->getPayload);
+
+        if (domainPacketType == PacketType::DomainConnectRequest) {
+            QUuid connectUUID;
 
-        if (domainPacketType == PacketTypeDomainConnectRequest) {
             if (!_domainHandler.getAssignmentUUID().isNull()) {
                 // this is a connect request and we're an assigned node
                 // so set our packetUUID as the assignment UUID
-                packetUUID = _domainHandler.getAssignmentUUID();
+                connectUUID = _domainHandler.getAssignmentUUID();
             } else if (_domainHandler.requiresICE()) {
                 // this is a connect request and we're an interface client
                 // that used ice to discover the DS
                 // so send our ICE client UUID with the connect request
-                packetUUID = _domainHandler.getICEClientID();
+                connectUUID = _domainHandler.getICEClientID();
             }
-        }
 
-        QByteArray domainServerPacket = byteArrayWithUUIDPopulatedHeader(domainPacketType, packetUUID);
-        QDataStream packetStream(&domainServerPacket, QIODevice::Append);
+            // pack the connect UUID for this connect request
+            packetStream << connectUUID;
+        }
 
         // pack our data to send to the domain-server
         packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
@@ -367,7 +369,7 @@ void NodeList::sendDomainServerCheckIn() {
         flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn);
 
         if (!isUsingDTLS) {
-            writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr());
+            sendPacket(domainPacket, _domainHandler.getSockAddr());
         }
 
         if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
@@ -410,7 +412,7 @@ void NodeList::sendDSPathQuery(const QString& newPath) {
     // only send a path query if we know who our DS is or is going to be
     if (_domainHandler.isSocketKnown()) {
         // construct the path query packet
-        QByteArray pathQueryPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerPathQuery);
+        auto pathQueryPacket = NLPacket::create(PacketType::DomainServerPathQuery);
 
         // get the UTF8 representation of path query
         QByteArray pathQueryUTF8 = newPath.toUtf8();
@@ -418,18 +420,18 @@ void NodeList::sendDSPathQuery(const QString& newPath) {
         // get the size of the UTF8 representation of the desired path
         quint16 numPathBytes = pathQueryUTF8.size();
 
-        if (pathQueryPacket.size() + numPathBytes + sizeof(numPathBytes) < MAX_PACKET_SIZE) {
+        if (numPathBytes + sizeof(numPathBytes) < pathQueryPacket.size() ) {
             // append the size of the path to the query packet
-            pathQueryPacket.append(reinterpret_cast<char*>(&numPathBytes), sizeof(numPathBytes));
+            pathQueryPacket.write(&numPathBytes, sizeof(numPathBytes));
 
             // append the path itself to the query packet
-            pathQueryPacket.append(pathQueryUTF8);
+            pathQueryPacket.write(pathQueryUTF8);
 
             qCDebug(networking) << "Sending a path query packet for path" << newPath << "to domain-server at"
                 << _domainHandler.getSockAddr();
 
             // send off the path query
-            writeUnverifiedDatagram(pathQueryPacket, _domainHandler.getSockAddr());
+            sendPacket(pathQueryPacket, _domainHandler.getSockAddr());
         } else {
             qCDebug(networking) << "Path" << newPath << "would make PacketTypeDomainServerPathQuery packet > MAX_PACKET_SIZE." <<
                 "Will not send query.";
diff --git a/libraries/networking/src/PacketByteArray.cpp b/libraries/networking/src/PacketByteArray.cpp
deleted file mode 100644
index 4d456b9066..0000000000
--- a/libraries/networking/src/PacketByteArray.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-//  PacketPayload.cpp
-//  libraries/networking/src
-//
-//  Created by Stephen Birarda on 07/06/15.
-//  Copyright 2015 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-#include "PacketPayload.h"
-
-PacketPayload::PacketPayload(char* data, int maxBytes) :
-    _data(data)
-    _maxBytes(maxBytes)
-{
-
-}
-
-int PacketPayload::append(const char* src, int srcBytes) {
-    // this is a call to write at the current index
-    int numWrittenBytes = write(src, srcBytes, _index);
-
-    if (numWrittenBytes > 0) {
-        // we wrote some bytes, push the index
-        _index += numWrittenBytes;
-        return numWrittenBytes;
-    } else {
-        return numWrittenBytes;
-    }
-}
-
-const int PACKET_WRITE_ERROR = -1;
-
-int PacketPayload::write(const char* src, int srcBytes, int index) {
-    if (index >= _maxBytes) {
-        // we were passed a bad index, return -1
-        return PACKET_WRITE_ERROR;
-    }
-
-    // make sure we have the space required to write this block
-    int bytesAvailable = _maxBytes - index;
-
-    if (bytesAvailable < srcBytes) {
-        // good to go - write the data
-        memcpy(_data + index, src, srcBytes);
-
-        // should this cause us to push our index (is this the farthest we've written in data)?
-        _index = std::max(_data + index + srcBytes, _index);
-
-        // return the number of bytes written
-        return srcBytes;
-    } else {
-        // not enough space left for this write - return an error
-        return PACKET_WRITE_ERROR;
-    }
-}
-
-
diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp
index 83b424d44d..02097402c6 100644
--- a/libraries/networking/src/PacketHeaders.cpp
+++ b/libraries/networking/src/PacketHeaders.cpp
@@ -31,7 +31,8 @@ const QSet<PacketType::Value> NON_VERIFIED_PACKETS = QSet<PacketType::Value>()
 
 const QSet<PacketType::Value> SEQUENCE_NUMBERED_PACKETS = QSet<PacketType::Value>() << AvatarData;
 
-const QSet<PacketType::Value> NON_SOURCED_PACKETS = QSet<PacketType::Value>() << ICEPing << ICEPingReply;
+const QSet<PacketType::Value> NON_SOURCED_PACKETS = QSet<PacketType::Value>()
+    << ICEPing << ICEPingReply << DomainConnectRequest;
 
 int arithmeticCodingValueFromBuffer(const char* checkValue) {
     if (((uchar) *checkValue) < 255) {
diff --git a/libraries/networking/src/PacketList.cpp b/libraries/networking/src/PacketList.cpp
index bb61dd39d1..31b793cc76 100644
--- a/libraries/networking/src/PacketList.cpp
+++ b/libraries/networking/src/PacketList.cpp
@@ -47,18 +47,15 @@ qint64 writeData(const char* data, qint64 maxSize) {
 
         if (!_isOrdered) {
             auto newPacket = T::create(_packetType);
-            PacketPayload& newPayload = newPacket.getPayload();
 
             if (_segmentStartIndex >= 0) {
                 // We in the process of writing a segment for an unordered PacketList.
                 // We need to try and pull the first part of the segment out to our new packet
 
-                PacketPayload& currentPayload = _currentPacket->getPayload();
-
                 // check now to see if this is an unsupported write
-                int numBytesToEnd = currentPayload.size() - _segmentStartIndex;
+                int numBytesToEnd = _currentPacket.size() - _segmentStartIndex;
 
-                if ((newPayload.size() - numBytesToEnd) < maxSize) {
+                if ((newPacket.size() - numBytesToEnd) < maxSize) {
                     // this is an unsupported case - the segment is bigger than the size of an individual packet
                     // but the PacketList is not going to be sent ordered
                     qDebug() << "Error in PacketList::writeData - attempted to write a segment to an unordered packet that is"
@@ -67,20 +64,20 @@ qint64 writeData(const char* data, qint64 maxSize) {
                 }
 
                 // copy from currentPacket where the segment started to the beginning of the newPacket
-                newPayload.write(currentPacket.constData() + _segmentStartIndex, numBytesToEnd);
+                newPacket.write(currentPacket.constData() + _segmentStartIndex, numBytesToEnd);
 
                 // the current segment now starts at the beginning of the new packet
                 _segmentStartIndex = 0;
 
                 // shrink the current payload to the actual size of the packet
-                currentPayload.setSizeUsed(_segmentStartIndex);
+                currentPacket.setSizeUsed(_segmentStartIndex);
             }
 
             // move the current packet to our list of packets
             _packets.insert(std::move(_currentPacket));
 
             // write the data to the newPacket
-            newPayload.write(data, maxSize);
+            newPacket.write(data, maxSize);
 
             // set our current packet to the new packet
             _currentPacket = newPacket;
@@ -90,9 +87,8 @@ qint64 writeData(const char* data, qint64 maxSize) {
         } else {
             // we're an ordered PacketList - let's fit what we can into the current packet and then put the leftover
             // into a new packet
-            PacketPayload& currentPayload = _currentPacket.getPayload();
 
-            int numBytesToEnd = _currentPayload.size() - _currentPayload.pos();
+            int numBytesToEnd = _currentPacket.size() - _currentPacket.sizeUsed();
             _currentPacket.write(data, numBytesToEnd);
 
             // move the current packet to our list of packets