diff --git a/bindings/notifications.go b/bindings/notifications.go
index 1bd400882d3ddf9981752d0937fd2e358071258f..bd7c75506b81945f0b0c3e293cd1f6b750ea1460 100644
--- a/bindings/notifications.go
+++ b/bindings/notifications.go
@@ -29,7 +29,23 @@ type Notifications interface {
 	AddToken(newToken, app string) error
 	// RemoveToken removes the given Token from the server
 	// It will remove all registered identities if it is the last Token
+
 	RemoveToken() error
+	// SetMaxState sets the maximum functional state of any identity
+	// downstream moduals will be told to clamp any state greater than maxState
+	// down to maxState. Depending on UX requirements, they may still show the
+	// state in an altered manner, for example greying out a description.
+	// This is designed so when the state is raised, the old configs are
+	// maintained.
+	// This will unregister / re-register with the push server when leaving or
+	// entering the Push maxState.
+	// The default maxState is Push
+	// will return an error if the maxState isnt a valid state
+	SetMaxState(maxState notifications.NotificationState) error
+
+	// GetMaxState returns the current MaxState
+	GetMaxState() notifications.NotificationState
+
 	// GetID returns the ID of the notifications object
 	GetID() int
 }
diff --git a/bindings/notifications_test.go b/bindings/notifications_test.go
deleted file mode 100644
index 66b6f63e558f4973eea60b2df6ff2784cac79391..0000000000000000000000000000000000000000
--- a/bindings/notifications_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2022 xx foundation                                             //
-//                                                                            //
-// Use of this source code is governed by a license that can be found in the  //
-// LICENSE file.                                                              //
-////////////////////////////////////////////////////////////////////////////////
-
-package bindings
-
-import (
-	"encoding/json"
-	"fmt"
-	"testing"
-
-	"gitlab.com/elixxir/client/v4/e2e/ratchet"
-	"gitlab.com/xx_network/primitives/id"
-)
-
-func TestNotificationReport(t *testing.T) {
-	var reports []NotificationReport
-
-	for i := 0; i < 3; i++ {
-		nr := NotificationReport{
-			ForMe:  true,
-			Type:   []string{ratchet.E2e},
-			Source: id.NewIdFromUInt(uint64(i), id.User, t).Bytes(),
-		}
-
-		reports = append(reports, nr)
-	}
-
-	nrs := NotificationReports(reports)
-
-	marshal, _ := json.MarshalIndent(nrs, " ", "	")
-	fmt.Printf("%s\n", marshal)
-}
diff --git a/notifications/interface_test.go b/notifications/interface_test.go
index 948fa4e27bb5972673604243b3a69c1f5ab9e2c5..ebdcdd687759e9be0fd70017796feb7ce1e15b54 100644
--- a/notifications/interface_test.go
+++ b/notifications/interface_test.go
@@ -2,6 +2,7 @@ package notifications
 
 import (
 	"gitlab.com/xx_network/primitives/id"
+	"math"
 	"testing"
 )
 
@@ -61,3 +62,18 @@ func TestNotificationState_String(t *testing.T) {
 		}
 	}
 }
+
+func TestNotificationState_IsValid(t *testing.T) {
+	inputs := []NotificationState{Mute, WhenOpen, Push, -100, 14, Mute - 1,
+		Push + 1, 1000, math.MaxInt64, math.MinInt64}
+	outputs := []bool{true, true, true, false, false, false, false, false,
+		false, false}
+
+	for idx, ns := range inputs {
+		result := ns.IsValid() == nil
+		if outputs[idx] != result {
+			t.Errorf("on test %d output did not match expected, "+
+				"expected: %t, received: %t", idx, outputs[idx], result)
+		}
+	}
+}
diff --git a/notifications/manager.go b/notifications/manager.go
index cd4f262de8af346a11b23f5385fdc8e31bbb6b7a..070e9de56e7aeaa2d9315090045bb456596d8507 100644
--- a/notifications/manager.go
+++ b/notifications/manager.go
@@ -230,6 +230,7 @@ func (m *manager) maxStateUpdate(key string, old, new *versioned.Object, op vers
 	if key != maxStateKey {
 		jww.ERROR.Printf("Got an update for the wrong key, "+
 			"expected: %s, got: %s", maxStateKey, key)
+		return
 	}
 
 	if op == versioned.Deleted {
@@ -242,6 +243,7 @@ func (m *manager) maxStateUpdate(key string, old, new *versioned.Object, op vers
 	if err := json.Unmarshal(new.Data, &m.maxState); err != nil {
 		jww.WARN.Printf("failed to unmarshal %s, ignoring: %+v",
 			maxStateKey, err)
+		return
 	}
 
 	for g := range m.callbacks {
@@ -260,7 +262,7 @@ func (m *manager) loadMaxStateUnsafe(obj *versioned.Object) {
 
 func (m *manager) setMaxStateUnsafe(max NotificationState) {
 
-	b, err := json.Marshal(&m.maxState)
+	b, err := json.Marshal(&max)
 	if err != nil {
 		jww.FATAL.Panicf("Failed to set max notifications sate to %s:"+
 			" %+v", max, err)
diff --git a/notifications/manager_test.go b/notifications/manager_test.go
index e0dfdd5d14a196f652c6fcb84e474a57ad791a5c..1dab4e4d187970407a8cccc0d1296375b18a1890 100644
--- a/notifications/manager_test.go
+++ b/notifications/manager_test.go
@@ -397,6 +397,123 @@ func getGroup(i, numGroups int) int {
 	return i % numGroups
 }
 
+func TestManager_maxStateUpdate(t *testing.T) {
+	m, _, _ := buildTestingManager(t)
+	mInternal := m.(*manager)
+
+	mInternal.maxStateUpdate("blah", nil, nil, versioned.Deleted)
+	// key check worked becasue we didnt crash from the delete panic
+
+	numGroups := 4
+	numCB := numGroups / 2
+	wg := &sync.WaitGroup{}
+
+	didRun := make([]bool, numGroups)
+	expectedRun := []bool{true, false, true, false}
+
+	var setMax NotificationState
+
+	for i := 0; i < numGroups; i++ {
+		groupName := fmt.Sprintf("grp_%d", i)
+		nid := id.NewIdFromUInt(uint64(i), id.User, t)
+		m.Set(nid, groupName, []byte{0}, NotificationState(i%3))
+		if i%2 == 0 {
+			localI := i
+			cb := func(group Group, created, edits, deletions []*id.ID,
+				maxState NotificationState) {
+				if created != nil || edits != nil || deletions != nil {
+					t.Errorf("actions are not nil")
+				}
+				if maxState != setMax {
+					t.Errorf("max state incorrect, expected %s, "+
+						"received %s", setMax, maxState)
+				}
+				didRun[localI] = true
+				wg.Done()
+			}
+			m.RegisterUpdateCallback(groupName, cb)
+		}
+	}
+
+	for i := Mute; i <= Push; i++ {
+		setMax = i
+		for j := versioned.Created; j <= versioned.Deleted; j++ {
+			ch := make(chan bool)
+			didRun = make([]bool, numGroups)
+			if j != versioned.Deleted {
+				wg.Add(numCB)
+			}
+
+			go func() {
+				defer func() {
+					if r := recover(); r != nil {
+						ch <- false
+					}
+				}()
+				mInternal.maxStateUpdate(maxStateKey, nil,
+					makeMaxStateObj(i, t), j)
+				ch <- true
+			}()
+			result := <-ch
+			if j == versioned.Deleted && result {
+				t.Errorf("did not panic on deletion")
+			} else if j != versioned.Deleted && !result {
+				t.Errorf("panicked on non deletion")
+			}
+			if j != versioned.Deleted {
+				wg.Wait()
+				if mInternal.maxState != i {
+					t.Errorf("max state not set to %s, is %s", i, mInternal.maxState)
+				}
+				if !reflect.DeepEqual(expectedRun, didRun) {
+					t.Errorf("wrong callbacks were called, %+v", didRun)
+				}
+			}
+
+		}
+	}
+
+}
+
+func makeMaxStateObj(max NotificationState, t *testing.T) *versioned.Object {
+	b, err := json.Marshal(&max)
+	if err != nil {
+		t.Fatalf("Failed to marshal max state: %+v", err)
+	}
+	return &versioned.Object{
+		Version:   maxStateKetVersion,
+		Timestamp: time.Now(),
+		Data:      b,
+	}
+}
+
+func TestManager_setLoadMaxState(t *testing.T) {
+	m, _, _ := buildTestingManager(t)
+	mInternal := m.(*manager)
+
+	mInternal.maxState = -1
+
+	tests := []NotificationState{Mute, WhenOpen, Push}
+
+	for _, ns := range tests {
+		mInternal.maxState = -1
+		mInternal.setMaxStateUnsafe(ns)
+		if mInternal.maxState != ns {
+			t.Errorf("In ram max state did not take with set")
+		}
+		mInternal.maxState = -1
+		obj, err := mInternal.remote.Get(maxStateKey, maxStateKetVersion)
+		if err != nil {
+			t.Errorf("Failed to get max state %s from ekv: %+v", ns, err)
+		}
+		mInternal.loadMaxStateUnsafe(obj)
+		if mInternal.maxState != ns {
+			t.Errorf("In ram max state did not take with load of %s",
+				string(obj.Data))
+		}
+	}
+}
+
 func TestManager_upsertNotificationUnsafeRAM(t *testing.T) {
 	m, _, _ := buildTestingManager(t)
 	mInternal := m.(*manager)
@@ -645,6 +762,7 @@ type commsMock struct {
 	receivedMessage interface{}
 	returnedMessage *messages.Ack
 	returnedError   error
+	numRuns         int
 }
 
 func initCommsMock() *commsMock {
@@ -664,6 +782,7 @@ func (cm *commsMock) RegisterToken(host *connect.Host,
 	message *pb.RegisterTokenRequest) (*messages.Ack, error) {
 	cm.receivedHost = host
 	cm.receivedMessage = message
+	cm.numRuns++
 	return cm.returnedMessage, cm.returnedError
 }
 
@@ -671,6 +790,7 @@ func (cm *commsMock) UnregisterToken(host *connect.Host,
 	message *pb.UnregisterTokenRequest) (*messages.Ack, error) {
 	cm.receivedHost = host
 	cm.receivedMessage = message
+	cm.numRuns++
 	return cm.returnedMessage, cm.returnedError
 }
 
@@ -678,6 +798,7 @@ func (cm *commsMock) RegisterTrackedID(host *connect.Host,
 	message *pb.RegisterTrackedIdRequest) (*messages.Ack, error) {
 	cm.receivedHost = host
 	cm.receivedMessage = message
+	cm.numRuns++
 	return cm.returnedMessage, cm.returnedError
 }
 
@@ -685,6 +806,7 @@ func (cm *commsMock) UnregisterTrackedID(host *connect.Host,
 	message *pb.UnregisterTrackedIdRequest) (*messages.Ack, error) {
 	cm.receivedHost = host
 	cm.receivedMessage = message
+	cm.numRuns++
 	return cm.returnedMessage, cm.returnedError
 }
 
diff --git a/notifications/maxState_test.go b/notifications/maxState_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..25e8e8a56bdcc5031ec650419d8fd0ccb6e45e23
--- /dev/null
+++ b/notifications/maxState_test.go
@@ -0,0 +1,101 @@
+package notifications
+
+import (
+	"encoding/json"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+func TestManager_SetMaxState(t *testing.T) {
+	m, _, comms := buildTestingManager(t)
+	mInternal := m.(*manager)
+
+	expectedLen := int(Push) + 1
+
+	comms.reset()
+
+	for i := Mute; i <= Push; i++ {
+		for x := 0; x < int(i)+1; x++ {
+			nid := id.NewIdFromUInt(uint64(int(i)*100+x), id.User, t)
+			if err := m.Set(nid, "a", []byte{0}, i); err != nil {
+				t.Errorf("errored in set: %+v", err)
+			}
+		}
+	}
+
+	if err := m.SetMaxState(Mute); err != nil {
+		t.Fatalf("errored in setMaxState: %+v", err)
+	}
+
+	unReg := comms.receivedMessage.(*pb.UnregisterTrackedIdRequest)
+	if len(unReg.Request.TrackedIntermediaryID) != expectedLen {
+		t.Errorf("wrong number of ids unregistered")
+	}
+
+	if mInternal.maxState != Mute {
+		t.Errorf("max state at wrong state internally")
+	}
+
+	if loadMaxState(mInternal, t) != Mute {
+		t.Errorf("max state at wrong state in ekv")
+	}
+
+	comms.reset()
+	if err := m.SetMaxState(WhenOpen); err != nil {
+		t.Fatalf("errored in setMaxState: %+v", err)
+	}
+	if comms.receivedMessage != nil {
+		t.Errorf("message sent when it shouldnt be!")
+	}
+	if mInternal.maxState != WhenOpen {
+		t.Errorf("max state at wrong state internally")
+	}
+
+	if loadMaxState(mInternal, t) != WhenOpen {
+		t.Errorf("max state at wrong state in ekv")
+	}
+	comms.reset()
+	if err := m.SetMaxState(Push); err != nil {
+		t.Fatalf("errored in setMaxState: %+v", err)
+	}
+
+	reg := comms.receivedMessage.(*pb.RegisterTrackedIdRequest)
+	if len(reg.Request.TrackedIntermediaryID) != expectedLen {
+		t.Errorf("wrong number of ids unregistered")
+	}
+
+	if mInternal.maxState != Push {
+		t.Errorf("max state at wrong state internally")
+	}
+
+	if loadMaxState(mInternal, t) != Push {
+		t.Errorf("max state at wrong state in ekv")
+	}
+}
+
+func TestManager_GetMaxState(t *testing.T) {
+	m, _, _ := buildTestingManager(t)
+	mInternal := m.(*manager)
+
+	for i := Mute; i <= Push; i++ {
+		mInternal.maxState = i
+		got := m.GetMaxState()
+		if i != got {
+			t.Errorf("get didnt match value")
+		}
+	}
+}
+
+func loadMaxState(m *manager, t *testing.T) NotificationState {
+	obj, err := m.remote.Get(maxStateKey, maxStateKetVersion)
+	if err != nil {
+		t.Fatalf("could not get max state: %+v", err)
+	}
+	var ms NotificationState
+	err = json.Unmarshal(obj.Data, &ms)
+	if err != nil {
+		t.Fatalf("could not get max state: %+v", err)
+	}
+	return ms
+}