diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
index fbfba41c33f27a069565af1bc8b0a6643c3de4a3..e301eea38b74887faaad1d49fd56565516090cca 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -166,11 +166,13 @@ public interface DatabaseComponent {
 	/** Returns the body of the message with the given ID. */
 	byte[] getMessageBody(MessageId m) throws DbException;
 
-	/** Returns the header of the message with the given ID. */
-	MessageHeader getMessageHeader(MessageId m) throws DbException;
-
 	/** Returns the headers of all messages in the given group. */
-	Collection<MessageHeader> getMessageHeaders(GroupId g) throws DbException;
+	Collection<GroupMessageHeader> getMessageHeaders(GroupId g)
+			throws DbException;
+
+	/** Returns the headers of all private messages. */
+	Collection<PrivateMessageHeader> getPrivateMessageHeaders()
+			throws DbException;
 
 	/** Returns the user's rating for the given author. */
 	Rating getRating(AuthorId a) throws DbException;
diff --git a/briar-api/src/net/sf/briar/api/db/GroupMessageHeader.java b/briar-api/src/net/sf/briar/api/db/GroupMessageHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..af8add02b337bac51c6ced34c8614dd34c8e4425
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/GroupMessageHeader.java
@@ -0,0 +1,31 @@
+package net.sf.briar.api.db;
+
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.GroupId;
+import net.sf.briar.api.messaging.MessageId;
+
+public class GroupMessageHeader extends MessageHeader {
+
+	private final GroupId groupId;
+	private final Author author;
+
+	public GroupMessageHeader(MessageId id, MessageId parent, String subject,
+			long timestamp, boolean read, boolean starred, GroupId groupId,
+			Author author) {
+		super(id, parent, subject, timestamp, read, starred);
+		this.groupId = groupId;
+		this.author = author;
+	}
+
+	/** Returns the ID of the group to which the message belongs. */
+	public GroupId getGroupId() {
+		return groupId;
+	}
+
+	/**
+	 * Returns the message's author, or null if this is an  anonymous message.
+	 */
+	public Author getAuthor() {
+		return author;
+	}
+}
diff --git a/briar-api/src/net/sf/briar/api/db/MessageHeader.java b/briar-api/src/net/sf/briar/api/db/MessageHeader.java
index 7d9190227708a906ba7f389f8295c176c118e234..b1f11d1bd549b5add829b7dbd69af065b3121d24 100644
--- a/briar-api/src/net/sf/briar/api/db/MessageHeader.java
+++ b/briar-api/src/net/sf/briar/api/db/MessageHeader.java
@@ -1,25 +1,18 @@
 package net.sf.briar.api.db;
 
-import net.sf.briar.api.messaging.AuthorId;
-import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.MessageId;
 
-public class MessageHeader {
+public abstract class MessageHeader {
 
 	private final MessageId id, parent;
-	private final GroupId group;
-	private final AuthorId author;
 	private final String subject;
 	private final long timestamp;
 	private final boolean read, starred;
 
-	public MessageHeader(MessageId id, MessageId parent, GroupId group,
-			AuthorId author, String subject, long timestamp, boolean read,
-			boolean starred) {
+	protected MessageHeader(MessageId id, MessageId parent, String subject,
+			long timestamp, boolean read, boolean starred) {
 		this.id = id;
 		this.parent = parent;
-		this.group = group;
-		this.author = author;
 		this.subject = subject;
 		this.timestamp = timestamp;
 		this.read = read;
@@ -39,22 +32,6 @@ public class MessageHeader {
 		return parent;
 	}
 
-	/**
-	 * Returns the ID of the group to which the message belongs, or null if
-	 * this is a private message.
-	 */
-	public GroupId getGroup() {
-		return group;
-	}
-
-	/**
-	 * Returns the ID of the message's author, or null if this is an
-	 * anonymous message.
-	 */
-	public AuthorId getAuthor() {
-		return author;
-	}
-
 	/** Returns the message's subject line. */
 	public String getSubject() {
 		return subject;
diff --git a/briar-api/src/net/sf/briar/api/db/PrivateMessageHeader.java b/briar-api/src/net/sf/briar/api/db/PrivateMessageHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..900c2cb2a92fa2c43c196594694ed8659d8381a2
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/PrivateMessageHeader.java
@@ -0,0 +1,31 @@
+package net.sf.briar.api.db;
+
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.messaging.MessageId;
+
+public class PrivateMessageHeader extends MessageHeader {
+
+	private final ContactId contactId;
+	private final boolean incoming;
+
+	public PrivateMessageHeader(MessageId id, MessageId parent, String subject,
+			long timestamp, boolean read, boolean starred, ContactId contactId,
+			boolean incoming) {
+		super(id, parent, subject, timestamp, read, starred);
+		this.contactId = contactId;
+		this.incoming = incoming;
+	}
+
+	/**
+	 * Returns the ID of the contact who is the sender (if incoming) or
+	 * recipient (if outgoing) of this message.
+	 */
+	public ContactId getContactId() {
+		return contactId;
+	}
+
+	/** Returns true if this is an incoming message. */
+	public boolean isIncoming() {
+		return incoming;
+	}
+}
diff --git a/briar-api/src/net/sf/briar/api/db/event/LocalRetentionTimeUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/LocalRetentionTimeUpdatedEvent.java
deleted file mode 100644
index c28ce107552f56a9d2bebac1e5adbeff6b351d9e..0000000000000000000000000000000000000000
--- a/briar-api/src/net/sf/briar/api/db/event/LocalRetentionTimeUpdatedEvent.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.sf.briar.api.db.event;
-
-/**
- * An event that is broadcast when the retention time of the local database
- * changes.
- */
-public class LocalRetentionTimeUpdatedEvent extends DatabaseEvent {
-
-}
diff --git a/briar-api/src/net/sf/briar/api/db/event/MessageExpiredEvent.java b/briar-api/src/net/sf/briar/api/db/event/MessageExpiredEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f130f78a7f5fb00f80c0197549168c2d69c2e02
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/event/MessageExpiredEvent.java
@@ -0,0 +1,9 @@
+package net.sf.briar.api.db.event;
+
+/**
+ * An event that is broadcast when one or messages expire from the database,
+ * potentially changing the database's retention time.
+ */
+public class MessageExpiredEvent extends DatabaseEvent {
+
+}
diff --git a/briar-api/src/net/sf/briar/api/messaging/Message.java b/briar-api/src/net/sf/briar/api/messaging/Message.java
index 5140ffc0e93a5919e83d3d6a1b725ca94b6aeafd..f2aac57fba2adfc47b144e07a5954398075d780e 100644
--- a/briar-api/src/net/sf/briar/api/messaging/Message.java
+++ b/briar-api/src/net/sf/briar/api/messaging/Message.java
@@ -12,16 +12,16 @@ public interface Message {
 	MessageId getParent();
 
 	/**
-	 * Returns the identifier of the {@link Group} to which the message
-	 * belongs, or null if this is a private message.
+	 * Returns the {@link Group} to which the message belongs, or null if this
+	 * is a private message.
 	 */
-	GroupId getGroup();
+	Group getGroup();
 
 	/**
-	 * Returns the identifier of the message's {@link Author}, or null if this
-	 * is an anonymous message.
+	 * Returns the message's {@link Author}, or null if this is an anonymous
+	 * message.
 	 */
-	AuthorId getAuthor();
+	Author getAuthor();
 
 	/** Returns the message's subject line. */
 	String getSubject();
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index 7fd09fa9a1856bd80dab1142abf66c2b234f0d98..f735dcc7654f1e4b3f80d52024a1b3498e4be27c 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -10,7 +10,8 @@ import net.sf.briar.api.Rating;
 import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.MessageHeader;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.db.PrivateMessageHeader;
 import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
@@ -264,18 +265,19 @@ interface Database<T> {
 	byte[] getMessageBody(T txn, MessageId m) throws DbException;
 
 	/**
-	 * Returns the header of the message identified by the given ID.
+	 * Returns the headers of all messages in the given group.
 	 * <p>
 	 * Locking: message read.
 	 */
-	MessageHeader getMessageHeader(T txn, MessageId m) throws DbException;
+	Collection<GroupMessageHeader> getMessageHeaders(T txn, GroupId g)
+			throws DbException;
 
 	/**
-	 * Returns the headers of all messages in the given group.
+	 * Returns the headers of all private messages.
 	 * <p>
 	 * Locking: message read.
 	 */
-	Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
+	Collection<PrivateMessageHeader> getPrivateMessageHeaders(T txn)
 			throws DbException;
 
 	/**
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index c4455971a9c5b4da12764d185b100f1ecd26209a..7cb268aaef2d1fa4e64a978fa64e0757b670dd51 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -30,19 +30,20 @@ import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.MessageHeader;
+import net.sf.briar.api.db.GroupMessageHeader;
 import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.NoSuchMessageException;
 import net.sf.briar.api.db.NoSuchSubscriptionException;
 import net.sf.briar.api.db.NoSuchTransportException;
+import net.sf.briar.api.db.PrivateMessageHeader;
 import net.sf.briar.api.db.event.ContactAddedEvent;
 import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
-import net.sf.briar.api.db.event.LocalRetentionTimeUpdatedEvent;
 import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
 import net.sf.briar.api.db.event.MessageAddedEvent;
+import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.MessageReceivedEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.db.event.RemoteRetentionTimeUpdatedEvent;
@@ -52,6 +53,7 @@ import net.sf.briar.api.db.event.TransportAddedEvent;
 import net.sf.briar.api.db.event.TransportRemovedEvent;
 import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.messaging.Ack;
+import net.sf.briar.api.messaging.Author;
 import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
@@ -258,7 +260,7 @@ DatabaseCleaner.Callback {
 					try {
 						// Don't store the message if the user has
 						// unsubscribed from the group
-						if(db.containsSubscription(txn, m.getGroup()))
+						if(db.containsSubscription(txn, m.getGroup().getId()))
 							added = storeGroupMessage(txn, m, null);
 						db.commitTransaction(txn);
 					} catch(DbException e) {
@@ -318,8 +320,8 @@ DatabaseCleaner.Callback {
 	private int calculateSendability(T txn, Message m) throws DbException {
 		int sendability = 0;
 		// One point for a good rating
-		AuthorId a = m.getAuthor();
-		if(a != null && db.getRating(txn, a) == GOOD) sendability++;
+		Author a = m.getAuthor();
+		if(a != null && db.getRating(txn, a.getId()) == GOOD) sendability++;
 		// One point per sendable child (backward inclusion)
 		sendability += db.getNumberOfSendableChildren(txn, m.getId());
 		return sendability;
@@ -445,10 +447,10 @@ DatabaseCleaner.Callback {
 	}
 
 	/**
+	 * If the given message is already in the database, returns false.
 	 * Otherwise stores the message and marks it as new or seen with respect to
 	 * the given contact, depending on whether the message is outgoing or
-	 * incoming, respectively; or returns false if the message is already in
-	 * the database.
+	 * incoming, respectively.
 	 * <p>
 	 * Locking: contact read, message write.
 	 */
@@ -457,9 +459,7 @@ DatabaseCleaner.Callback {
 		if(m.getGroup() != null) throw new IllegalArgumentException();
 		if(m.getAuthor() != null) throw new IllegalArgumentException();
 		if(!db.addPrivateMessage(txn, m, c)) return false;
-		MessageId id = m.getId();
-		if(incoming) db.addStatus(txn, c, id, true);
-		else db.addStatus(txn, c, id, false);
+		db.addStatus(txn, c, m.getId(), incoming);
 		// Count the bytes stored
 		synchronized(spaceLock) {
 			bytesStoredSinceLastCheck += m.getSerialised().length;
@@ -878,16 +878,18 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public MessageHeader getMessageHeader(MessageId m) throws DbException {
+	public Collection<GroupMessageHeader> getMessageHeaders(GroupId g)
+			throws DbException {
 		messageLock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				if(!db.containsMessage(txn, m))
-					throw new NoSuchMessageException();
-				MessageHeader h = db.getMessageHeader(txn, m);
+				if(!db.containsSubscription(txn, g))
+					throw new NoSuchSubscriptionException();
+				Collection<GroupMessageHeader> headers =
+						db.getMessageHeaders(txn, g);
 				db.commitTransaction(txn);
-				return h;
+				return headers;
 			} catch(DbException e) {
 				db.abortTransaction(txn);
 				throw e;
@@ -897,16 +899,14 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public Collection<MessageHeader> getMessageHeaders(GroupId g)
+	public Collection<PrivateMessageHeader> getPrivateMessageHeaders()
 			throws DbException {
 		messageLock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				if(!db.containsSubscription(txn, g))
-					throw new NoSuchSubscriptionException();
-				Collection<MessageHeader> headers =
-						db.getMessageHeaders(txn, g);
+				Collection<PrivateMessageHeader> headers =
+						db.getPrivateMessageHeaders(txn);
 				db.commitTransaction(txn);
 				return headers;
 			} catch(DbException e) {
@@ -1285,9 +1285,9 @@ DatabaseCleaner.Callback {
 	private boolean storeMessage(T txn, ContactId c, Message m)
 			throws DbException {
 		if(m.getTimestamp() > clock.currentTimeMillis()) return false;
-		GroupId g = m.getGroup();
+		Group g = m.getGroup();
 		if(g == null) return storePrivateMessage(txn, m, c, true);
-		if(!db.containsVisibleSubscription(txn, c, g)) return false;
+		if(!db.containsVisibleSubscription(txn, c, g.getId())) return false;
 		return storeGroupMessage(txn, m, c);
 	}
 
@@ -1846,7 +1846,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		if(removed) callListeners(new LocalRetentionTimeUpdatedEvent());
+		if(removed) callListeners(new MessageExpiredEvent());
 		return removed;
 	}
 
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 0ea09d93ddf8585ba28f30e2904bb010afc69cb8..4be4420fd19ad1c4230d0e45868dd91ce18305c9 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -1,6 +1,7 @@
 package net.sf.briar.db;
 
 import static java.sql.Types.BINARY;
+import static java.sql.Types.VARCHAR;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static net.sf.briar.api.Rating.UNRATED;
@@ -34,7 +35,9 @@ import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.db.DbClosedException;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.MessageHeader;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.db.PrivateMessageHeader;
+import net.sf.briar.api.messaging.Author;
 import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
@@ -117,7 +120,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " (messageId HASH NOT NULL,"
 					+ " parentId HASH," // Null for the first msg in a thread
 					+ " groupId HASH," // Null for private messages
-					+ " authorId HASH," // Null for private or anonymous msgs
+					+ " authorId HASH," // Null for private/anon messages
+					+ " authorName VARCHAR," // Null for private/anon messages
+					+ " authorKey VARCHAR," // Null for private/anon messages
 					+ " subject VARCHAR NOT NULL,"
 					+ " timestamp BIGINT NOT NULL,"
 					+ " length INT NOT NULL,"
@@ -620,29 +625,38 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public boolean addGroupMessage(Connection txn, Message m)
 			throws DbException {
-		assert m.getGroup() != null;
+		if(m.getGroup() == null) throw new IllegalArgumentException();
 		if(containsMessage(txn, m.getId())) return false;
 		PreparedStatement ps = null;
 		try {
 			String sql = "INSERT INTO messages (messageId, parentId, groupId,"
-					+ " authorId, subject, timestamp, length, bodyStart,"
-					+ " bodyLength, raw, sendability, read, starred)"
-					+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ZERO(), FALSE,"
-					+ " FALSE)";
+					+ " authorId, authorName, authorKey, subject, timestamp,"
+					+ " length, bodyStart, bodyLength, raw, sendability, read,"
+					+ " starred)"
+					+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ZERO(),"
+					+ " FALSE, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getId().getBytes());
 			if(m.getParent() == null) ps.setNull(2, BINARY);
 			else ps.setBytes(2, m.getParent().getBytes());
-			ps.setBytes(3, m.getGroup().getBytes());
-			if(m.getAuthor() == null) ps.setNull(4, BINARY);
-			else ps.setBytes(4, m.getAuthor().getBytes());
-			ps.setString(5, m.getSubject());
-			ps.setLong(6, m.getTimestamp());
+			ps.setBytes(3, m.getGroup().getId().getBytes());
+			Author a = m.getAuthor();
+			if(a == null) {
+				ps.setNull(4, BINARY);
+				ps.setNull(5, VARCHAR);
+				ps.setNull(6, BINARY);
+			} else {
+				ps.setBytes(4, a.getId().getBytes());
+				ps.setString(5, a.getName());
+				ps.setBytes(6, a.getPublicKey());
+			}
+			ps.setString(7, m.getSubject());
+			ps.setLong(8, m.getTimestamp());
 			byte[] raw = m.getSerialised();
-			ps.setInt(7, raw.length);
-			ps.setInt(8, m.getBodyStart());
-			ps.setInt(9, m.getBodyLength());
-			ps.setBytes(10, raw);
+			ps.setInt(9, raw.length);
+			ps.setInt(10, m.getBodyStart());
+			ps.setInt(11, m.getBodyLength());
+			ps.setBytes(12, raw);
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -686,13 +700,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public boolean addPrivateMessage(Connection txn, Message m, ContactId c)
 			throws DbException {
-		assert m.getGroup() == null;
+		if(m.getGroup() != null) throw new IllegalArgumentException();
 		if(containsMessage(txn, m.getId())) return false;
 		PreparedStatement ps = null;
 		try {
-			String sql = "INSERT INTO messages"
-					+ " (messageId, parentId, subject, timestamp, length,"
-					+ " bodyStart, bodyLength, raw, contactId, read, starred)"
+			String sql = "INSERT INTO messages (messageId, parentId, subject,"
+					+ " timestamp, length, bodyStart, bodyLength, raw,"
+					+ " contactId, read, starred)"
 					+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getId().getBytes());
@@ -1211,33 +1225,42 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public MessageHeader getMessageHeader(Connection txn, MessageId m)
-			throws DbException {
+	public Collection<GroupMessageHeader> getMessageHeaders(Connection txn,
+			GroupId g) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT parentId, authorId, groupId, subject,"
-					+ " timestamp, read, starred"
+			String sql = "SELECT messageId, parentId, authorId, authorName,"
+					+ " authorKey, subject, timestamp, read, starred"
 					+ " FROM messages"
-					+ " WHERE messageId = ?";
+					+ " WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, m.getBytes());
+			ps.setBytes(1, g.getBytes());
 			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			byte[] b = rs.getBytes(1);
-			MessageId parent = b == null ? null : new MessageId(b);
-			AuthorId author = new AuthorId(rs.getBytes(2));
-			b = rs.getBytes(3);
-			GroupId group = b == null ? null : new GroupId(b);
-			String subject = rs.getString(4);
-			long timestamp = rs.getLong(5);
-			boolean read = rs.getBoolean(6);
-			boolean starred = rs.getBoolean(7);
-			if(rs.next()) throw new DbStateException();
+			List<GroupMessageHeader> headers =
+					new ArrayList<GroupMessageHeader>();
+			while(rs.next()) {
+				MessageId id = new MessageId(rs.getBytes(1));
+				byte[] b = rs.getBytes(2);
+				MessageId parent = b == null ? null : new MessageId(b);
+				Author author = null;
+				b = rs.getBytes(3);
+				if(b != null) {
+					AuthorId authorId = new AuthorId(b);
+					String authorName = rs.getString(4);
+					byte[] authorKey = rs.getBytes(5);
+					author = new Author(authorId, authorName, authorKey);
+				}
+				String subject = rs.getString(6);
+				long timestamp = rs.getLong(7);
+				boolean read = rs.getBoolean(8);
+				boolean starred = rs.getBoolean(9);
+				headers.add(new GroupMessageHeader(id, parent, subject,
+						timestamp, read, starred, g, author));
+			}
 			rs.close();
 			ps.close();
-			return new MessageHeader(m, parent, group, author, subject,
-					timestamp, read, starred);
+			return Collections.unmodifiableList(headers);
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1245,30 +1268,33 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<MessageHeader> getMessageHeaders(Connection txn,
-			GroupId g) throws DbException {
+	public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
+			Connection txn) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT messageId, parentId, authorId, subject,"
-					+ " timestamp, read, starred"
-					+ " FROM messages"
-					+ " WHERE groupId = ?";
+			String sql = "SELECT m.messageId, parentId, subject, timestamp,"
+					+ " contactId, read, starred, seen"
+					+ " FROM messages AS m"
+					+ " JOIN statuses AS s"
+					+ " ON m.messageId = s.messageId"
+					+ " WHERE groupId IS NULL";
 			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, g.getBytes());
 			rs = ps.executeQuery();
-			List<MessageHeader> headers = new ArrayList<MessageHeader>();
+			List<PrivateMessageHeader> headers =
+					new ArrayList<PrivateMessageHeader>();
 			while(rs.next()) {
 				MessageId id = new MessageId(rs.getBytes(1));
-				byte[] p = rs.getBytes(2);
-				MessageId parent = p == null ? null : new MessageId(p);
-				AuthorId author = new AuthorId(rs.getBytes(3));
-				String subject = rs.getString(4);
-				long timestamp = rs.getLong(5);
+				byte[] b = rs.getBytes(2);
+				MessageId parent = b == null ? null : new MessageId(b);
+				String subject = rs.getString(3);
+				long timestamp = rs.getLong(4);
+				ContactId contactId = new ContactId(rs.getInt(5));
 				boolean read = rs.getBoolean(6);
 				boolean starred = rs.getBoolean(7);
-				headers.add(new MessageHeader(id, parent, g, author, subject,
-						timestamp, read, starred));
+				boolean seen = rs.getBoolean(8);
+				headers.add(new PrivateMessageHeader(id, parent, subject,
+						timestamp, read, starred, contactId, !seen));
 			}
 			rs.close();
 			ps.close();
diff --git a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
index c6c6011b462cadd842da85d4de0d9d70be5f74ec..16f0411243601532836e5813a46716f8445ffd1d 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
@@ -20,9 +20,7 @@ import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.MessageDigest;
 import net.sf.briar.api.messaging.Author;
-import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.Group;
-import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageFactory;
 import net.sf.briar.api.messaging.MessageId;
@@ -150,12 +148,9 @@ class MessageFactoryImpl implements MessageFactory {
 		}
 		// Hash the message, including the signatures, to get the message ID
 		w.removeConsumer(digestingConsumer);
-		byte[] raw = out.toByteArray();
 		MessageId id = new MessageId(messageDigest.digest());
-		GroupId groupId = group == null ? null : group.getId();
-		AuthorId authorId = author == null ? null : author.getId();
-		return new MessageImpl(id, parent, groupId, authorId, subject,
-				timestamp, raw, bodyStart, body.length);
+		return new MessageImpl(id, parent, group, author, subject,
+				timestamp, out.toByteArray(), bodyStart, body.length);
 	}
 
 	private void writeGroup(Writer w, Group g) throws IOException {
diff --git a/briar-core/src/net/sf/briar/messaging/MessageImpl.java b/briar-core/src/net/sf/briar/messaging/MessageImpl.java
index e7090a9612dcbf17067131b0308298e02a259ceb..3472613223a9090e575442c51979b25d1eae5edd 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageImpl.java
@@ -1,8 +1,8 @@
 package net.sf.briar.messaging;
 
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
-import net.sf.briar.api.messaging.AuthorId;
-import net.sf.briar.api.messaging.GroupId;
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 
@@ -10,15 +10,15 @@ import net.sf.briar.api.messaging.MessageId;
 class MessageImpl implements Message {
 
 	private final MessageId id, parent;
-	private final GroupId group;
-	private final AuthorId author;
+	private final Group group;
+	private final Author author;
 	private final String subject;
 	private final long timestamp;
 	private final byte[] raw;
 	private final int bodyStart, bodyLength;
 
-	public MessageImpl(MessageId id, MessageId parent, GroupId group,
-			AuthorId author, String subject, long timestamp, byte[] raw,
+	public MessageImpl(MessageId id, MessageId parent, Group group,
+			Author author, String subject, long timestamp, byte[] raw,
 			int bodyStart, int bodyLength) {
 		if(bodyStart + bodyLength > raw.length)
 			throw new IllegalArgumentException();
@@ -43,11 +43,11 @@ class MessageImpl implements Message {
 		return parent;
 	}
 
-	public GroupId getGroup() {
+	public Group getGroup() {
 		return group;
 	}
 
-	public AuthorId getAuthor() {
+	public Author getAuthor() {
 		return author;
 	}
 
diff --git a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
index 1296481152f6b38ada4945a93319d6d854ace09f..e1dc4896e70b94ae2e22bb86692c2ee544451d0e 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
@@ -8,9 +8,7 @@ import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.KeyParser;
 import net.sf.briar.api.crypto.MessageDigest;
 import net.sf.briar.api.messaging.Author;
-import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.Group;
-import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.MessageVerifier;
@@ -55,10 +53,7 @@ class MessageVerifierImpl implements MessageVerifier {
 			if(!signature.verify(m.getGroupSignature()))
 				throw new GeneralSecurityException();
 		}
-		GroupId groupId = group == null ? null : group.getId();
-		AuthorId authorId = author == null ? null : author.getId();
-		return new MessageImpl(id, m.getParent(), groupId, authorId,
-				m.getSubject(), m.getTimestamp(), raw, m.getBodyStart(),
-				m.getBodyLength());
+		return new MessageImpl(id, m.getParent(), group, author, m.getSubject(),
+				m.getTimestamp(), raw, m.getBodyStart(), m.getBodyLength());
 	}
 }
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
index 42d0acb290b4152d7fb439ff2f7265c9187239a0..310c4d304c9d14ae9e42fc354f52bebf2827a252 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
@@ -29,7 +29,7 @@ import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
-import net.sf.briar.api.db.event.LocalRetentionTimeUpdatedEvent;
+import net.sf.briar.api.db.event.MessageExpiredEvent;
 import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
 import net.sf.briar.api.db.event.MessageAddedEvent;
@@ -134,7 +134,7 @@ abstract class DuplexConnection implements DatabaseListener {
 		if(e instanceof ContactRemovedEvent) {
 			ContactRemovedEvent c = (ContactRemovedEvent) e;
 			if(contactId.equals(c.getContactId())) dispose(false, true);
-		} else if(e instanceof LocalRetentionTimeUpdatedEvent) {
+		} else if(e instanceof MessageExpiredEvent) {
 			dbExecutor.execute(new GenerateRetentionUpdate());
 		} else if(e instanceof LocalSubscriptionsUpdatedEvent) {
 			LocalSubscriptionsUpdatedEvent l =
diff --git a/briar-tests/src/net/sf/briar/TestMessage.java b/briar-tests/src/net/sf/briar/TestMessage.java
index 881b02777a95167b750f8381790df831d4985cc1..0c4ae532a9e201036ec24d5901456b977d0b4389 100644
--- a/briar-tests/src/net/sf/briar/TestMessage.java
+++ b/briar-tests/src/net/sf/briar/TestMessage.java
@@ -3,28 +3,28 @@ package net.sf.briar;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 
-import net.sf.briar.api.messaging.AuthorId;
-import net.sf.briar.api.messaging.GroupId;
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 
 public class TestMessage implements Message {
 
 	private final MessageId id, parent;
-	private final GroupId group;
-	private final AuthorId author;
+	private final Group group;
+	private final Author author;
 	private final String subject;
 	private final long timestamp;
 	private final byte[] raw;
 	private final int bodyStart, bodyLength;
 
-	public TestMessage(MessageId id, MessageId parent, GroupId group,
-			AuthorId author, String subject, long timestamp, byte[] raw) {
+	public TestMessage(MessageId id, MessageId parent, Group group,
+			Author author, String subject, long timestamp, byte[] raw) {
 		this(id, parent, group, author, subject, timestamp, raw, 0, raw.length);
 	}
 
-	public TestMessage(MessageId id, MessageId parent, GroupId group,
-			AuthorId author, String subject, long timestamp, byte[] raw,
+	public TestMessage(MessageId id, MessageId parent, Group group,
+			Author author, String subject, long timestamp, byte[] raw,
 			int bodyStart, int bodyLength) {
 		this.id = id;
 		this.parent = parent;
@@ -45,11 +45,11 @@ public class TestMessage implements Message {
 		return parent;
 	}
 
-	public GroupId getGroup() {
+	public Group getGroup() {
 		return group;
 	}
 
-	public AuthorId getAuthor() {
+	public Author getAuthor() {
 		return author;
 	}
 
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index db67d27c52133d62df2fb83d19809eeb1a6457f1..1cf9044ffa54b389ef8759186aa3d3ac4b373d77 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -29,6 +29,7 @@ import net.sf.briar.api.db.event.MessageAddedEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.messaging.Ack;
+import net.sf.briar.api.messaging.Author;
 import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
@@ -53,43 +54,46 @@ import org.junit.Test;
 public abstract class DatabaseComponentTest extends BriarTestCase {
 
 	protected final Object txn = new Object();
-	protected final AuthorId authorId;
-	protected final ContactId contactId;
 	protected final GroupId groupId;
+	protected final Group group;
+	protected final AuthorId authorId;
+	protected final Author author;
 	protected final MessageId messageId, messageId1;
-	private final String contactName, subject;
-	private final long timestamp;
-	private final int size;
-	private final byte[] raw;
-	private final Contact contact;
-	private final Message message, privateMessage;
-	private final Group group;
-	private final TransportId transportId;
-	private final TransportProperties transportProperties;
-	private final Endpoint endpoint;
-	private final TemporarySecret temporarySecret;
+	protected final String subject;
+	protected final long timestamp;
+	protected final int size;
+	protected final byte[] raw;
+	protected final Message message, privateMessage;
+	protected final TransportId transportId;
+	protected final TransportProperties transportProperties;
+	protected final ContactId contactId;
+	protected final String contactName;
+	protected final Contact contact;
+	protected final Endpoint endpoint;
+	protected final TemporarySecret temporarySecret;
 
 	public DatabaseComponentTest() {
 		super();
-		authorId = new AuthorId(TestUtils.getRandomId());
-		contactId = new ContactId(234);
 		groupId = new GroupId(TestUtils.getRandomId());
+		group = new Group(groupId, "Group name", null);
+		authorId = new AuthorId(TestUtils.getRandomId());
+		author = new Author(authorId, "Alice", new byte[60]);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
-		contactName = "Alice";
 		subject = "Foo";
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
-		contact = new Contact(contactId, contactName, timestamp);
-		message = new TestMessage(messageId, null, groupId, authorId, subject,
+		message = new TestMessage(messageId, null, group, author, subject,
 				timestamp, raw);
 		privateMessage = new TestMessage(messageId, null, null, null, subject,
 				timestamp, raw);
-		group = new Group(groupId, "The really exciting group", null);
 		transportId = new TransportId(TestUtils.getRandomId());
-		transportProperties = new TransportProperties(
-				Collections.singletonMap("foo", "bar"));
+		transportProperties = new TransportProperties(Collections.singletonMap(
+				"foo", "bar"));
+		contactId = new ContactId(234);
+		contactName = "Alice";
+		contact = new Contact(contactId, contactName, timestamp);
 		endpoint = new Endpoint(contactId, transportId, 123, 234, 345, true);
 		temporarySecret = new TemporarySecret(contactId, transportId, 1, 2,
 				3, false, 4, new byte[32], 5, 6, new byte[4]);
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index 37cc20a629b2ad97235ebaa45c56575219ae5808..ec00dd3f61c2d5f858832727622e8ee0c94bdcd0 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -28,7 +28,8 @@ import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.clock.SystemClock;
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.db.MessageHeader;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.messaging.Author;
 import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
@@ -50,37 +51,40 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	private final File testDir = TestUtils.getTestDirectory();
 	private final Random random = new Random();
+	private final GroupId groupId;
 	private final Group group;
 	private final AuthorId authorId;
-	private final ContactId contactId;
-	private final GroupId groupId;
+	private final Author author;
 	private final MessageId messageId, messageId1;
-	private final String contactName, subject;
+	private final String subject;
 	private final long timestamp;
 	private final int size;
 	private final byte[] raw;
 	private final Message message, privateMessage;
 	private final TransportId transportId;
+	private final ContactId contactId;
+	private final String contactName;
 
 	public H2DatabaseTest() throws Exception {
 		super();
-		authorId = new AuthorId(TestUtils.getRandomId());
-		contactId = new ContactId(1);
 		groupId = new GroupId(TestUtils.getRandomId());
+		group = new Group(groupId, "Group name", null);
+		authorId = new AuthorId(TestUtils.getRandomId());
+		author = new Author(authorId, "Alice", new byte[60]);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
-		group = new Group(groupId, "Foo", null);
-		contactName = "Alice";
 		subject = "Foo";
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
 		random.nextBytes(raw);
-		message = new TestMessage(messageId, null, groupId, authorId, subject,
+		message = new TestMessage(messageId, null, group, author, subject,
 				timestamp, raw);
 		privateMessage = new TestMessage(messageId1, null, null, null,
 				subject, timestamp, raw);
 		transportId = new TransportId(TestUtils.getRandomId());
+		contactId = new ContactId(1);
+		contactName = "Alice";
 	}
 
 	@Before
@@ -545,8 +549,9 @@ public class H2DatabaseTest extends BriarTestCase {
 	@Test
 	public void testGetMessagesByAuthor() throws Exception {
 		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
+		Author author1 = new Author(authorId1, "Bob", new byte[60]);
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
-		Message message1 = new TestMessage(messageId1, null, groupId, authorId1,
+		Message message1 = new TestMessage(messageId1, null, group, author1,
 				subject, timestamp, raw);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
@@ -577,15 +582,14 @@ public class H2DatabaseTest extends BriarTestCase {
 		MessageId childId2 = new MessageId(TestUtils.getRandomId());
 		MessageId childId3 = new MessageId(TestUtils.getRandomId());
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = new Group(groupId1, "Another group name",
-				null);
-		Message child1 = new TestMessage(childId1, messageId, groupId,
-				authorId, subject, timestamp, raw);
-		Message child2 = new TestMessage(childId2, messageId, groupId,
-				authorId, subject, timestamp, raw);
+		Group group1 = new Group(groupId1, "Group name", null);
+		Message child1 = new TestMessage(childId1, messageId, group, author,
+				subject, timestamp, raw);
+		Message child2 = new TestMessage(childId2, messageId, group, author,
+				subject, timestamp, raw);
 		// The third child is in a different group
-		Message child3 = new TestMessage(childId3, messageId, groupId1,
-				authorId, subject, timestamp, raw);
+		Message child3 = new TestMessage(childId3, messageId, group1, author,
+				subject, timestamp, raw);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -615,7 +619,7 @@ public class H2DatabaseTest extends BriarTestCase {
 	@Test
 	public void testGetOldMessages() throws Exception {
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
-		Message message1 = new TestMessage(messageId1, null, groupId, authorId,
+		Message message1 = new TestMessage(messageId1, null, group, author,
 				subject, timestamp + 1000, raw);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
@@ -646,7 +650,7 @@ public class H2DatabaseTest extends BriarTestCase {
 	public void testGetFreeSpace() throws Exception {
 		byte[] largeBody = new byte[ONE_MEGABYTE];
 		for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i;
-		Message message1 = new TestMessage(messageId, null, groupId, authorId,
+		Message message1 = new TestMessage(messageId, null, group, author,
 				subject, timestamp, largeBody);
 		Database<Connection> db = open(false);
 
@@ -1126,7 +1130,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// A message with no parent should return null
 		MessageId childId = new MessageId(TestUtils.getRandomId());
-		Message child = new TestMessage(childId, null, groupId, null, subject,
+		Message child = new TestMessage(childId, null, group, null, subject,
 				timestamp, raw);
 		db.addGroupMessage(txn, child);
 		assertTrue(db.containsMessage(txn, childId));
@@ -1147,7 +1151,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		// A message with an absent parent should return null
 		MessageId childId = new MessageId(TestUtils.getRandomId());
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
-		Message child = new TestMessage(childId, parentId, groupId, null,
+		Message child = new TestMessage(childId, parentId, group, null,
 				subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		assertTrue(db.containsMessage(txn, childId));
@@ -1173,9 +1177,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		// A message with a parent in another group should return null
 		MessageId childId = new MessageId(TestUtils.getRandomId());
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
-		Message child = new TestMessage(childId, parentId, groupId, null,
+		Message child = new TestMessage(childId, parentId, group, null,
 				subject, timestamp, raw);
-		Message parent = new TestMessage(parentId, null, groupId1, null,
+		Message parent = new TestMessage(parentId, null, group1, null,
 				subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		db.addGroupMessage(txn, parent);
@@ -1198,8 +1202,8 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// A message with a private parent should return null
 		MessageId childId = new MessageId(TestUtils.getRandomId());
-		Message child = new TestMessage(childId, messageId1, groupId,
-				null, subject, timestamp, raw);
+		Message child = new TestMessage(childId, messageId1, group, null,
+				subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		db.addPrivateMessage(txn, privateMessage, contactId);
 		assertTrue(db.containsMessage(txn, childId));
@@ -1222,10 +1226,10 @@ public class H2DatabaseTest extends BriarTestCase {
 		// A message with a parent in the same group should return the parent
 		MessageId childId = new MessageId(TestUtils.getRandomId());
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
-		Message child = new TestMessage(childId, parentId, groupId, null,
-				subject, timestamp, raw);
-		Message parent = new TestMessage(parentId, null, groupId, null,
+		Message child = new TestMessage(childId, parentId, group, null,
 				subject, timestamp, raw);
+		Message parent = new TestMessage(parentId, null, group, null, subject,
+				timestamp, raw);
 		db.addGroupMessage(txn, child);
 		db.addGroupMessage(txn, parent);
 		assertTrue(db.containsMessage(txn, childId));
@@ -1247,7 +1251,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// Store a couple of messages
 		int bodyLength = raw.length - 20;
-		Message message1 = new TestMessage(messageId, null, groupId, null,
+		Message message1 = new TestMessage(messageId, null, group, null,
 				subject, timestamp, raw, 5, bodyLength);
 		Message privateMessage1 = new TestMessage(messageId1, null, null,
 				null, subject, timestamp, raw, 10, bodyLength);
@@ -1289,19 +1293,20 @@ public class H2DatabaseTest extends BriarTestCase {
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
 		long timestamp1 = System.currentTimeMillis();
-		Message message1 = new TestMessage(messageId1, parentId, groupId,
-				authorId, subject, timestamp1, raw);
+		Message message1 = new TestMessage(messageId1, parentId, group, author,
+				subject, timestamp1, raw);
 		db.addGroupMessage(txn, message1);
 		// Mark one of the messages read
 		assertFalse(db.setReadFlag(txn, messageId, true));
 
 		// Retrieve the message headers
-		Collection<MessageHeader> headers = db.getMessageHeaders(txn, groupId);
-		Iterator<MessageHeader> it = headers.iterator();
+		Collection<GroupMessageHeader> headers =
+				db.getMessageHeaders(txn, groupId);
+		Iterator<GroupMessageHeader> it = headers.iterator();
 		boolean messageFound = false, message1Found = false;
 		// First header (order is undefined)
 		assertTrue(it.hasNext());
-		MessageHeader header = it.next();
+		GroupMessageHeader header = it.next();
 		if(messageId.equals(header.getId())) {
 			assertHeadersMatch(message, header);
 			assertTrue(header.getRead());
@@ -1340,12 +1345,11 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.close();
 	}
 
-	private void assertHeadersMatch(Message m, MessageHeader h) {
+	private void assertHeadersMatch(Message m, GroupMessageHeader h) {
 		assertEquals(m.getId(), h.getId());
 		if(m.getParent() == null) assertNull(h.getParent());
 		else assertEquals(m.getParent(), h.getParent());
-		if(m.getGroup() == null) assertNull(h.getGroup());
-		else assertEquals(m.getGroup(), h.getGroup());
+		assertEquals(m.getGroup().getId(), h.getGroupId());
 		if(m.getAuthor() == null) assertNull(h.getAuthor());
 		else assertEquals(m.getAuthor(), h.getAuthor());
 		assertEquals(m.getSubject(), h.getSubject());
@@ -1412,20 +1416,20 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Subscribe to a couple of groups
 		db.addSubscription(txn, group);
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = new Group(groupId1, "Another group", null);
+		Group group1 = new Group(groupId1, "Group name", null);
 		db.addSubscription(txn, group1);
 
 		// Store two messages in the first group
 		db.addGroupMessage(txn, message);
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
-		Message message1 = new TestMessage(messageId1, null, groupId,
-				authorId, subject, timestamp, raw);
+		Message message1 = new TestMessage(messageId1, null, group, author,
+				subject, timestamp, raw);
 		db.addGroupMessage(txn, message1);
 
 		// Store one message in the second group
 		MessageId messageId2 = new MessageId(TestUtils.getRandomId());
-		Message message2 = new TestMessage(messageId2, null, groupId1,
-				authorId, subject, timestamp, raw);
+		Message message2 = new TestMessage(messageId2, null, group1, author,
+				subject, timestamp, raw);
 		db.addGroupMessage(txn, message2);
 
 		// Mark one of the messages in the first group read