diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
index 21043297bb338e51de4b76a73800dd970207280a..9789a59e0ac2e31ddd1e46a7ad7bb69c1fe78bbe 100644
--- a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
@@ -3,19 +3,23 @@ package org.briarproject.api.privategroup;
 import org.briarproject.api.clients.PostHeader;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.Author.Status;
+import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+@NotNullByDefault
 public class GroupMessageHeader extends PostHeader {
 
 	private final GroupId groupId;
 
-	public GroupMessageHeader(@NotNull GroupId groupId, @NotNull MessageId id,
+	public GroupMessageHeader(GroupId groupId, MessageId id,
 			@Nullable MessageId parentId, long timestamp,
-			@NotNull Author author, @NotNull Status authorStatus,
-			boolean read) {
+			Author author, Status authorStatus, boolean read) {
 		super(id, parentId, timestamp, author, authorStatus, read);
 		this.groupId = groupId;
 	}
diff --git a/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef8c3b337d72124fd5d42b075f5e8edea894a53f
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java
@@ -0,0 +1,21 @@
+package org.briarproject.api.privategroup;
+
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.nullsafety.NotNullByDefault;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.Nullable;
+
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+@NotNullByDefault
+public class JoinMessageHeader extends GroupMessageHeader {
+
+	public JoinMessageHeader(GroupId groupId, MessageId id,
+			@Nullable MessageId parentId, long timestamp, Author author,
+			Author.Status authorStatus, boolean read) {
+		super(groupId, id, parentId, timestamp, author, authorStatus, read);
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java
index 0f28ce927165b868bd784867d101856f39099e26..b67d715d4841bb6e3a451e802455c3a794c792f8 100644
--- a/briar-core/src/org/briarproject/privategroup/Constants.java
+++ b/briar-core/src/org/briarproject/privategroup/Constants.java
@@ -8,6 +8,8 @@ interface Constants {
 	String KEY_TYPE = "type";
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_READ = MSG_KEY_READ;
+	String KEY_PARENT_ID = "parentId";
+	String KEY_AUTHOR_ID = "authorId";
 	String KEY_AUTHOR_NAME = "authorName";
 	String KEY_AUTHOR_PUBLIC_KEY = "authorPublicKey";
 
diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
index 1bd94100b04a7c1a0c198d25b2d7a7001b6f7ffc..1fcaec82d3bb6fb4bb4178072c36652347b760af 100644
--- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
+++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
@@ -6,6 +6,8 @@ import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.privategroup.MessageType;
 import org.briarproject.api.privategroup.PrivateGroup;
 import org.briarproject.api.privategroup.PrivateGroupFactory;
@@ -24,8 +26,10 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENG
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
+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_READ;
 import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP;
 import static org.briarproject.privategroup.Constants.KEY_TYPE;
@@ -33,12 +37,14 @@ import static org.briarproject.privategroup.Constants.KEY_TYPE;
 class GroupMessageValidator extends BdfMessageValidator {
 
 	private final PrivateGroupFactory groupFactory;
+	private final AuthorFactory authorFactory;
 
 	GroupMessageValidator(PrivateGroupFactory groupFactory,
 			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
-			Clock clock) {
+			Clock clock, AuthorFactory authorFactory) {
 		super(clientHelper, metadataEncoder, clock);
 		this.groupFactory = groupFactory;
+		this.authorFactory = authorFactory;
 	}
 
 	@Override
@@ -179,6 +185,7 @@ class GroupMessageValidator extends BdfMessageValidator {
 
 		// Return the metadata and dependencies
 		BdfDictionary meta = new BdfDictionary();
+		if (parent_id != null) meta.put(KEY_PARENT_ID, parent_id);
 		return new BdfMessageContext(meta, dependencies);
 	}
 
@@ -186,6 +193,8 @@ class GroupMessageValidator extends BdfMessageValidator {
 			byte[] pubKey, long time) {
 		c.getDictionary().put(KEY_TIMESTAMP, time);
 		c.getDictionary().put(KEY_READ, false);
+		Author a = authorFactory.createAuthor(authorName, pubKey);
+		c.getDictionary().put(KEY_AUTHOR_ID, a.getId());
 		c.getDictionary().put(KEY_AUTHOR_NAME, authorName);
 		c.getDictionary().put(KEY_AUTHOR_PUBLIC_KEY, pubKey);
 	}
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
index 78c2693f6968e9859afa9fdc80fb878627b89412..8910913538a21ead4a38be84208f6aae53e06c62 100644
--- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
@@ -9,9 +9,13 @@ import org.briarproject.api.data.MetadataParser;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.Author.Status;
+import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.privategroup.GroupMessage;
 import org.briarproject.api.privategroup.GroupMessageHeader;
-import org.briarproject.api.privategroup.MessageType;
+import org.briarproject.api.privategroup.JoinMessageHeader;
 import org.briarproject.api.privategroup.PrivateGroup;
 import org.briarproject.api.privategroup.PrivateGroupFactory;
 import org.briarproject.api.privategroup.PrivateGroupManager;
@@ -26,14 +30,23 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
 import static org.briarproject.api.identity.Author.Status.OURSELVES;
+import static org.briarproject.api.privategroup.MessageType.JOIN;
+import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER;
+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_PREVIOUS_MSG_ID;
 import static org.briarproject.privategroup.Constants.KEY_READ;
 import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP;
@@ -49,17 +62,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 					+ "67657220627920546f727374656e2047"));
 
 	private final PrivateGroupFactory privateGroupFactory;
+	private final IdentityManager identityManager;
 
 	@Inject
 	PrivateGroupManagerImpl(ClientHelper clientHelper,
 			MetadataParser metadataParser, DatabaseComponent db,
-			PrivateGroupFactory privateGroupFactory) {
+			PrivateGroupFactory privateGroupFactory,
+			IdentityManager identityManager) {
 		super(db, clientHelper, metadataParser);
 
 		this.privateGroupFactory = privateGroupFactory;
+		this.identityManager = identityManager;
 	}
 
-	@NotNull
 	@Override
 	public ClientId getClientId() {
 		return CLIENT_ID;
@@ -85,14 +100,14 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	private void announceNewMember(Transaction txn, GroupMessage m)
 			throws DbException, FormatException {
 		BdfDictionary meta = new BdfDictionary();
-		meta.put(KEY_TYPE, MessageType.NEW_MEMBER.getInt());
+		meta.put(KEY_TYPE, NEW_MEMBER.getInt());
 		clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
 	}
 
 	private void joinPrivateGroup(Transaction txn, GroupMessage m)
 			throws DbException, FormatException {
 		BdfDictionary meta = new BdfDictionary();
-		meta.put(KEY_TYPE, MessageType.JOIN.getInt());
+		meta.put(KEY_TYPE, JOIN.getInt());
 		addMessageMetadata(meta, m, true);
 		clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
 		trackOutgoingMessage(txn, m.getMessage());
@@ -135,6 +150,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		clientHelper.mergeGroupMetadata(txn, g, d);
 	}
 
+	@Override
 	public long getMessageTimestamp(MessageId id) throws DbException {
 		try {
 			BdfDictionary d = clientHelper.getMessageMetadataAsDictionary(id);
@@ -150,7 +166,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		Transaction txn = db.startTransaction(false);
 		try {
 			BdfDictionary meta = new BdfDictionary();
-			meta.put(KEY_TYPE, MessageType.POST.getInt());
+			meta.put(KEY_TYPE, POST.getInt());
+			if (m.getParent() != null) meta.put(KEY_PARENT_ID, m.getParent());
 			addMessageMetadata(meta, m, true);
 			clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
 			trackOutgoingMessage(txn, m.getMessage());
@@ -165,7 +182,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 				m.getMessage().getTimestamp(), m.getMember(), OURSELVES, true);
 	}
 
-	@NotNull
 	@Override
 	public PrivateGroup getPrivateGroup(GroupId g) throws DbException {
 		PrivateGroup privateGroup;
@@ -179,7 +195,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		return privateGroup;
 	}
 
-	@NotNull
 	@Override
 	public PrivateGroup getPrivateGroup(Transaction txn, GroupId g)
 			throws DbException {
@@ -191,7 +206,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		}
 	}
 
-	@NotNull
 	@Override
 	public Collection<PrivateGroup> getPrivateGroups() throws DbException {
 		Collection<Group> groups;
@@ -219,18 +233,90 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		return false;
 	}
 
-	@NotNull
 	@Override
 	public String getMessageBody(MessageId m) throws DbException {
-		return "empty";
+		try {
+			// TODO remove
+			if (clientHelper.getMessageMetadataAsDictionary(m).getLong(KEY_TYPE) != POST.getInt())
+				return "new member joined";
+
+			// type(0), member_name(1), member_public_key(2), parent_id(3),
+			// previous_message_id(4), content(5), signature(6)
+			return clientHelper.getMessageAsList(m).getString(5);
+		} catch (FormatException e) {
+			throw new DbException(e);
+		}
 	}
 
-	@NotNull
 	@Override
 	public Collection<GroupMessageHeader> getHeaders(GroupId g)
 			throws DbException {
+		Collection<GroupMessageHeader> headers =
+				new ArrayList<GroupMessageHeader>();
+		Transaction txn = db.startTransaction(true);
+		try {
+			Map<MessageId, BdfDictionary> metadata =
+					clientHelper.getMessageMetadataAsDictionary(txn, g);
+			// get all authors we need to get the status for
+			Set<AuthorId> authors = new HashSet<AuthorId>();
+			for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
+				BdfDictionary meta = entry.getValue();
+				if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt())
+					continue;
+				byte[] idBytes = meta.getRaw(KEY_AUTHOR_ID);
+				authors.add(new AuthorId(idBytes));
+			}
+			// get statuses for all authors
+			Map<AuthorId, Status> statuses = new HashMap<AuthorId, Status>();
+			for (AuthorId id : authors) {
+				statuses.put(id, identityManager.getAuthorStatus(txn, id));
+			}
+			// Parse the metadata
+			for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
+				BdfDictionary meta = entry.getValue();
+				if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt())
+					continue;
+				headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta,
+						statuses));
+			}
+			txn.setComplete();
+			return headers;
+		} catch (FormatException e) {
+			throw new DbException(e);
+		} finally {
+			db.endTransaction(txn);
+		}
+	}
 
-		return Collections.emptyList();
+	private GroupMessageHeader getGroupMessageHeader(Transaction txn, GroupId g,
+			MessageId id, BdfDictionary meta, Map<AuthorId, Status> statuses)
+			throws DbException, FormatException {
+
+		MessageId parentId = null;
+		if (meta.containsKey(KEY_PARENT_ID)) {
+			parentId = new MessageId(meta.getRaw(KEY_PARENT_ID));
+		}
+		long timestamp = meta.getLong(KEY_TIMESTAMP);
+
+		AuthorId authorId = new AuthorId(meta.getRaw(KEY_AUTHOR_ID));
+		String name = meta.getString(KEY_AUTHOR_NAME);
+		byte[] publicKey = meta.getRaw(KEY_AUTHOR_PUBLIC_KEY);
+		Author author = new Author(authorId, name, publicKey);
+
+		Status status;
+		if (statuses.containsKey(authorId)) {
+			status = statuses.get(authorId);
+		} else {
+			status = identityManager.getAuthorStatus(txn, author.getId());
+		}
+		boolean read = meta.getBoolean(KEY_READ);
+
+		if (meta.getLong(KEY_TYPE) == JOIN.getInt()) {
+			return new JoinMessageHeader(g, id, parentId, timestamp, author,
+					status, read);
+		}
+		return new GroupMessageHeader(g, id, parentId, timestamp, author,
+				status, read);
 	}
 
 	@Override
@@ -251,6 +337,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 			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());
 	}
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java
index 55fa0894a653e4541dec26253ef99db850684608..49c0714c5cb0987c2fd5c602578ec498e42efdf0 100644
--- a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java
@@ -3,6 +3,7 @@ package org.briarproject.privategroup;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.ConversationManager;
 import org.briarproject.api.privategroup.GroupMessageFactory;
@@ -59,11 +60,15 @@ public class PrivateGroupModule {
 	GroupMessageValidator provideGroupMessageValidator(
 			PrivateGroupFactory groupFactory,
 			ValidationManager validationManager, ClientHelper clientHelper,
-			MetadataEncoder metadataEncoder, Clock clock) {
+			MetadataEncoder metadataEncoder, Clock clock,
+			AuthorFactory authorFactory) {
+
 		GroupMessageValidator validator = new GroupMessageValidator(
-				groupFactory, clientHelper, metadataEncoder, clock);
+				groupFactory, clientHelper, metadataEncoder, clock,
+				authorFactory);
 		validationManager.registerMessageValidator(
 				PrivateGroupManagerImpl.CLIENT_ID, validator);
+
 		return validator;
 	}