diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java
index b67d715d4841bb6e3a451e802455c3a794c792f8..3c83d232618f074ac612e095eba4772aebf9bd95 100644
--- a/briar-core/src/org/briarproject/privategroup/Constants.java
+++ b/briar-core/src/org/briarproject/privategroup/Constants.java
@@ -8,11 +8,11 @@ interface Constants {
 	String KEY_TYPE = "type";
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_READ = MSG_KEY_READ;
-	String KEY_PARENT_ID = "parentId";
+	String KEY_PARENT_MSG_ID = "parentMsgId";
+	String KEY_NEW_MEMBER_MSG_ID = "newMemberMsgId";
+	String KEY_PREVIOUS_MSG_ID = "previousMsgId";
 	String KEY_AUTHOR_ID = "authorId";
 	String KEY_AUTHOR_NAME = "authorName";
 	String KEY_AUTHOR_PUBLIC_KEY = "authorPublicKey";
 
-	// Messaging Group Metadata
-	String KEY_PREVIOUS_MSG_ID = "previousMsgId";
 }
diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
index 1fcaec82d3bb6fb4bb4178072c36652347b760af..01385793966e2ad89f6321c4a84edb67909c237b 100644
--- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
+++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
@@ -29,7 +29,9 @@ import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_
 import static org.briarproject.privategroup.Constants.KEY_AUTHOR_ID;
 import static org.briarproject.privategroup.Constants.KEY_AUTHOR_NAME;
 import static org.briarproject.privategroup.Constants.KEY_AUTHOR_PUBLIC_KEY;
-import static org.briarproject.privategroup.Constants.KEY_PARENT_ID;
+import static org.briarproject.privategroup.Constants.KEY_NEW_MEMBER_MSG_ID;
+import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID;
+import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
 import static org.briarproject.privategroup.Constants.KEY_READ;
 import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP;
 import static org.briarproject.privategroup.Constants.KEY_TYPE;
@@ -80,6 +82,8 @@ class GroupMessageValidator extends BdfMessageValidator {
 				break;
 			case POST:
 				c = validatePost(m, g, body, member_name, member_public_key);
+				addMessageMetadata(c, member_name, member_public_key,
+						m.getTimestamp());
 				break;
 			default:
 				throw new InvalidMessageException("Unknown Message Type");
@@ -141,6 +145,7 @@ class GroupMessageValidator extends BdfMessageValidator {
 
 		// Return the metadata and dependencies
 		BdfDictionary meta = new BdfDictionary();
+		meta.put(KEY_NEW_MEMBER_MSG_ID, new_member_id);
 		return new BdfMessageContext(meta, dependencies);
 	}
 
@@ -185,7 +190,8 @@ class GroupMessageValidator extends BdfMessageValidator {
 
 		// Return the metadata and dependencies
 		BdfDictionary meta = new BdfDictionary();
-		if (parent_id != null) meta.put(KEY_PARENT_ID, parent_id);
+		if (parent_id != null) meta.put(KEY_PARENT_MSG_ID, parent_id);
+		meta.put(KEY_PREVIOUS_MSG_ID, previous_message_id);
 		return new BdfMessageContext(meta, dependencies);
 	}
 
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
index caf8b81ae4a0614cf53129525e9e13faaadae2c4..eb3a888f0406ddd475772b8efafd74946911d4b6 100644
--- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
@@ -16,6 +16,7 @@ import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.privategroup.GroupMessage;
 import org.briarproject.api.privategroup.GroupMessageHeader;
 import org.briarproject.api.privategroup.JoinMessageHeader;
+import org.briarproject.api.privategroup.MessageType;
 import org.briarproject.api.privategroup.PrivateGroup;
 import org.briarproject.api.privategroup.PrivateGroupFactory;
 import org.briarproject.api.privategroup.PrivateGroupManager;
@@ -28,6 +29,7 @@ import org.briarproject.clients.BdfIncomingMessageHook;
 import org.briarproject.util.StringUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -45,7 +47,8 @@ import static org.briarproject.api.privategroup.MessageType.POST;
 import static org.briarproject.privategroup.Constants.KEY_AUTHOR_ID;
 import static org.briarproject.privategroup.Constants.KEY_AUTHOR_NAME;
 import static org.briarproject.privategroup.Constants.KEY_AUTHOR_PUBLIC_KEY;
-import static org.briarproject.privategroup.Constants.KEY_PARENT_ID;
+import static org.briarproject.privategroup.Constants.KEY_NEW_MEMBER_MSG_ID;
+import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID;
 import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
 import static org.briarproject.privategroup.Constants.KEY_READ;
 import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP;
@@ -100,6 +103,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 			throws DbException, FormatException {
 		BdfDictionary meta = new BdfDictionary();
 		meta.put(KEY_TYPE, NEW_MEMBER.getInt());
+		addMessageMetadata(meta, m, true);
 		clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
 	}
 
@@ -166,9 +170,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		try {
 			BdfDictionary meta = new BdfDictionary();
 			meta.put(KEY_TYPE, POST.getInt());
-			if (m.getParent() != null) meta.put(KEY_PARENT_ID, m.getParent());
+			if (m.getParent() != null) meta.put(KEY_PARENT_MSG_ID, m.getParent());
 			addMessageMetadata(meta, m, true);
 			clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
+			setPreviousMsgId(txn, m.getMessage().getGroupId(),
+					m.getMessage().getId());
 			trackOutgoingMessage(txn, m.getMessage());
 			txn.setComplete();
 		} catch (FormatException e) {
@@ -181,6 +187,15 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 				m.getMessage().getTimestamp(), m.getMember(), OURSELVES, true);
 	}
 
+	private void addMessageMetadata(BdfDictionary meta, GroupMessage m,
+			boolean read) {
+		meta.put(KEY_TIMESTAMP, m.getMessage().getTimestamp());
+		meta.put(KEY_READ, read);
+		meta.put(KEY_AUTHOR_ID, m.getMember().getId());
+		meta.put(KEY_AUTHOR_NAME, m.getMember().getName());
+		meta.put(KEY_AUTHOR_PUBLIC_KEY, m.getMember().getPublicKey());
+	}
+
 	@Override
 	public PrivateGroup getPrivateGroup(GroupId g) throws DbException {
 		PrivateGroup privateGroup;
@@ -288,8 +303,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 			throws DbException, FormatException {
 
 		MessageId parentId = null;
-		if (meta.containsKey(KEY_PARENT_ID)) {
-			parentId = new MessageId(meta.getRaw(KEY_PARENT_ID));
+		if (meta.containsKey(KEY_PARENT_MSG_ID)) {
+			parentId = new MessageId(meta.getRaw(KEY_PARENT_MSG_ID));
 		}
 		long timestamp = meta.getLong(KEY_TIMESTAMP);
 
@@ -318,23 +333,86 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
 			BdfDictionary meta) throws DbException, FormatException {
 
-		trackIncomingMessage(txn, m);
-
-		// TODO POST timestamp must be greater than the timestamps of the parent post, if any, and the member's previous message
-
-		// TODO JOIN timestamp must be equal to the timestamp of the new member message.
-		// TODO JOIN new_member_id must be the identifier of a NEW_MEMBER message with the same member_name and member_public_key
-
-		return true;
-	}
-
-	private void addMessageMetadata(BdfDictionary meta, GroupMessage m,
-			boolean read) {
-		meta.put(KEY_TIMESTAMP, m.getMessage().getTimestamp());
-		meta.put(KEY_READ, read);
-		meta.put(KEY_AUTHOR_ID, m.getMember().getId());
-		meta.put(KEY_AUTHOR_NAME, m.getMember().getName());
-		meta.put(KEY_AUTHOR_PUBLIC_KEY, m.getMember().getPublicKey());
+		long timestamp = meta.getLong(KEY_TIMESTAMP);
+		MessageType type =
+				MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
+		switch (type) {
+			case NEW_MEMBER:
+				// don't track incoming message, because it won't show in the UI
+				return true;
+			case JOIN:
+				// new_member_id must be the identifier of a NEW_MEMBER message
+				byte[] newMemberIdBytes =
+						meta.getOptionalRaw(KEY_NEW_MEMBER_MSG_ID);
+				MessageId newMemberId = new MessageId(newMemberIdBytes);
+				BdfDictionary newMemberMeta = clientHelper
+						.getMessageMetadataAsDictionary(txn, newMemberId);
+				MessageType newMemberType = MessageType
+						.valueOf(newMemberMeta.getLong(KEY_TYPE).intValue());
+				if (newMemberType != NEW_MEMBER) {
+					// FIXME throw new InvalidMessageException() (#643)
+					db.deleteMessage(txn, m.getId());
+					return false;
+				}
+				// timestamp must be equal to timestamp of NEW_MEMBER message
+				if (timestamp != newMemberMeta.getLong(KEY_TIMESTAMP)) {
+					// FIXME throw new InvalidMessageException() (#643)
+					db.deleteMessage(txn, m.getId());
+					return false;
+				}
+				// NEW_MEMBER must have same member_name and member_public_key
+				if (!Arrays.equals(meta.getRaw(KEY_AUTHOR_ID),
+						newMemberMeta.getRaw(KEY_AUTHOR_ID))) {
+					// FIXME throw new InvalidMessageException() (#643)
+					db.deleteMessage(txn, m.getId());
+					return false;
+				}
+				// TODO add to member list
+				trackIncomingMessage(txn, m);
+				return true;
+			case POST:
+				// timestamp must be greater than the timestamps of parent post
+				byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID);
+				if (parentIdBytes != null) {
+					MessageId parentId = new MessageId(parentIdBytes);
+					BdfDictionary parentMeta = clientHelper
+							.getMessageMetadataAsDictionary(txn, parentId);
+					if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP)) {
+						// FIXME throw new InvalidMessageException() (#643)
+						db.deleteMessage(txn, m.getId());
+						return false;
+					}
+					MessageType parentType = MessageType
+							.valueOf(parentMeta.getLong(KEY_TYPE).intValue());
+					if (parentType != POST) {
+						// FIXME throw new InvalidMessageException() (#643)
+						db.deleteMessage(txn, m.getId());
+						return false;
+					}
+				}
+				// and the member's previous message
+				byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID);
+				MessageId previousMsgId = new MessageId(previousMsgIdBytes);
+				BdfDictionary previousMeta = clientHelper
+						.getMessageMetadataAsDictionary(txn, previousMsgId);
+				if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP)) {
+					// FIXME throw new InvalidMessageException() (#643)
+					db.deleteMessage(txn, m.getId());
+					return false;
+				}
+				// previous message must be from same member
+				if (!Arrays.equals(meta.getRaw(KEY_AUTHOR_ID),
+						previousMeta.getRaw(KEY_AUTHOR_ID))) {
+					// FIXME throw new InvalidMessageException() (#643)
+					db.deleteMessage(txn, m.getId());
+					return false;
+				}
+				trackIncomingMessage(txn, m);
+				return true;
+			default:
+				// the validator should only let valid types pass
+				throw new RuntimeException("Unknown MessageType");
+		}
 	}
 
 }