diff --git a/cmix/client.go b/cmix/client.go
index 9df2adce41927f91a692b0d55b2043610ebf0471..d164b4d2a60620176fe7fee12c5a26e95e351c99 100644
--- a/cmix/client.go
+++ b/cmix/client.go
@@ -189,7 +189,6 @@ func (c *client) initialize(ndf *ndf.NetworkDefinition) error {
 	// Set up critical message tracking (sendCmix only)
 	critSender := func(msg format.Message, recipient *id.ID, params CMIXParams,
 	) (id.Round, ephemeral.Id, error) {
-		// TODO: Does this need to be reworked to take in a message compiler?  This has ramifications down the stack in critical messaging
 		compiler := func(round id.Round) (format.Message, error) {
 			return msg, nil
 		}
diff --git a/cmix/interface.go b/cmix/interface.go
index a5db8b3cfe2089e94d03861769785ab99bd4390a..717dbb0bbe8b26a288aaa79427bc6d2ed07a30a6 100644
--- a/cmix/interface.go
+++ b/cmix/interface.go
@@ -290,6 +290,14 @@ type Client interface {
 
 type ClientErrorReport func(source, message, trace string)
 
+// MessageAssembler func accepts a round ID, returning fingerprint, service, payload & mac.
+// This allows users to pass in a paylaod which will contain the round ID over which the message is sent.
+type MessageAssembler func(rid id.Round) (fingerprint format.Fingerprint, service message.Service, payload, mac []byte)
+
+// messageAssembler is an internal wrapper around MessageAssembler which returns a format.message
+// This is necessary to preserve the interaction between sendCmixHelper and critical messages
+type messageAssembler func(rid id.Round) (format.Message, error)
+
 type clientCommsInterface interface {
 	followNetworkComms
 	SendCmixCommsInterface
diff --git a/cmix/sendCmix.go b/cmix/sendCmix.go
index 0742a6fe30f554e70b57d97a1577d151411f9171..648e1ef1c8691211f40bf11fc76e86bc17e5dfe7 100644
--- a/cmix/sendCmix.go
+++ b/cmix/sendCmix.go
@@ -57,25 +57,48 @@ import (
 func (c *client) Send(recipient *id.ID, fingerprint format.Fingerprint,
 	service message.Service, payload, mac []byte, cmixParams CMIXParams) (
 	id.Round, ephemeral.Id, error) {
-	compiler := func(rid id.Round) (format.Fingerprint, message.Service, []byte, []byte) {
+	// create an internal assembler function to pass to sendWithAssembler
+	assembler := func(rid id.Round) (format.Fingerprint, message.Service, []byte, []byte) {
 		return fingerprint, service, payload, mac
 	}
-	return c.SendWithCompiler(recipient, compiler, cmixParams)
+	return c.sendWithAssembler(recipient, assembler, cmixParams)
 }
 
-type Compiler func(rid id.Round) (fingerprint format.Fingerprint, service message.Service, payload, mac []byte)
-
-type messageCompiler func(rid id.Round) (format.Message, error)
+// SendWithAssembler sends a variable cmix payload to the provided recipient.
+// The payload sent is based on the Complier function passed in, which accepts
+// a round ID and returns the necessary payload data.
+// Returns the round ID of the round the payload was sent or an error if it
+// fails.
+// This does not have end-to-end encryption on it and is used exclusively as
+// a send for higher order cryptographic protocols. Do not use unless
+// implementing a protocol on top.
+//   recipient - cMix ID of the recipient.
+//   assembler - MessageAssembler function, accepting round ID and returning fingerprint
+//   format.Fingerprint, service message.Service, payload, mac []byte
+// Will return an error if the network is unhealthy or if it fails to send
+// (along with the reason). Blocks until successful sends or errors.
+// WARNING: Do not roll your own crypto.
+func (c *client) SendWithAssembler(recipient *id.ID, assembler MessageAssembler, cmixParams CMIXParams) (
+	id.Round, ephemeral.Id, error) {
+	// Critical messaging and assembler-based message payloads are not compatible
+	if cmixParams.Critical {
+		return 0, ephemeral.Id{}, errors.New("Cannot send critical messages with a message assembler")
+	}
+	return c.sendWithAssembler(recipient, assembler, cmixParams)
+}
 
-func (c *client) SendWithCompiler(recipient *id.ID, compiler Compiler, cmixParams CMIXParams) (
+// sendWithAssembler wraps the passed in MessageAssembler in a messageAssembler for sendCmixHelper,
+// and sets up critical message handling where applicable.
+func (c *client) sendWithAssembler(recipient *id.ID, assembler MessageAssembler, cmixParams CMIXParams) (
 	id.Round, ephemeral.Id, error) {
 	if !c.Monitor.IsHealthy() {
 		return 0, ephemeral.Id{}, errors.New(
 			"Cannot send cmix message when the network is not healthy")
 	}
 
-	compilerFunc := func(rid id.Round) (format.Message, error) {
-		fingerprint, service, payload, mac := compiler(rid)
+	// Create an internal messageAssembler which returns a format.Message
+	assemblerFunc := func(rid id.Round) (format.Message, error) {
+		fingerprint, service, payload, mac := assembler(rid)
 
 		if len(payload) != c.maxMsgLen {
 			return format.Message{}, errors.Errorf(
@@ -100,7 +123,7 @@ func (c *client) SendWithCompiler(recipient *id.ID, compiler Compiler, cmixParam
 		return msg, nil
 	}
 
-	rid, ephID, msg, rtnErr := sendCmixHelper(c.Sender, compilerFunc, recipient, cmixParams,
+	rid, ephID, msg, rtnErr := sendCmixHelper(c.Sender, assemblerFunc, recipient, cmixParams,
 		c.instance, c.session.GetCmixGroup(), c.Registrar, c.rng, c.events,
 		c.session.GetTransmissionID(), c.comms)
 
@@ -121,7 +144,7 @@ func (c *client) SendWithCompiler(recipient *id.ID, compiler Compiler, cmixParam
 // If the message is successfully sent, the ID of the round sent it is returned,
 // which can be registered with the network instance to get a callback on its
 // status.
-func sendCmixHelper(sender gateway.Sender, msgCompiler messageCompiler, recipient *id.ID,
+func sendCmixHelper(sender gateway.Sender, assembler messageAssembler, recipient *id.ID,
 	cmixParams CMIXParams, instance *network.Instance, grp *cyclic.Group,
 	nodes nodes.Registrar, rng *fastRNG.StreamGenerator, events event.Reporter,
 	senderId *id.ID, comms SendCmixCommsInterface) (id.Round, ephemeral.Id, format.Message, error) {
@@ -198,7 +221,7 @@ func sendCmixHelper(sender gateway.Sender, msgCompiler messageCompiler, recipien
 			continue
 		}
 
-		msg, err := msgCompiler(id.Round(bestRound.ID))
+		msg, err := assembler(id.Round(bestRound.ID))
 		if err != nil {
 			jww.ERROR.Printf("Failed to compile message: %+v", err)
 			return 0, ephemeral.Id{}, format.Message{}, err