diff --git a/api/net/sf/briar/api/protocol/MessageEncoder.java b/api/net/sf/briar/api/protocol/MessageEncoder.java
index c11c2bc14cbf4bbef0538008d6dd27f5493f47d1..e1322cbedcb994d31547e243af608d17b9ec28a3 100644
--- a/api/net/sf/briar/api/protocol/MessageEncoder.java
+++ b/api/net/sf/briar/api/protocol/MessageEncoder.java
@@ -6,7 +6,11 @@ import java.security.PrivateKey;
 
 public interface MessageEncoder {
 
-	/** Encodes an anonymous to an unrestricted group. */
+	/** Encodes a private message. */
+	Message encodeMessage(MessageId parent, byte[] body) throws IOException,
+	GeneralSecurityException;
+
+	/** Encodes an anonymous message to an unrestricted group. */
 	Message encodeMessage(MessageId parent, Group group, byte[] body)
 	throws IOException, GeneralSecurityException;
 
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index d4891e7e70584ea908d7aa95a75fac334b687f02..099ac144f462ff199769d3785bc3936850a8e176 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -53,7 +53,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		"CREATE TABLE messages"
 		+ " (messageId HASH NOT NULL,"
 		+ " parentId HASH,"
-		+ " groupId HASH NOT NULL,"
+		+ " groupId HASH,"
 		+ " authorId HASH,"
 		+ " timestamp BIGINT NOT NULL,"
 		+ " size INT NOT NULL,"
diff --git a/components/net/sf/briar/protocol/MessageEncoderImpl.java b/components/net/sf/briar/protocol/MessageEncoderImpl.java
index 0a5b92a0a7ad00247d02b119b2030e3d3a5562ec..f36e94a2227304131a2979c2b7ada59a8571f0a8 100644
--- a/components/net/sf/briar/protocol/MessageEncoderImpl.java
+++ b/components/net/sf/briar/protocol/MessageEncoderImpl.java
@@ -33,6 +33,11 @@ class MessageEncoderImpl implements MessageEncoder {
 		this.writerFactory = writerFactory;
 	}
 
+	public Message encodeMessage(MessageId parent, byte[] body)
+	throws IOException, GeneralSecurityException {
+		return encodeMessage(parent, null, null, null, null, body);
+	}
+
 	public Message encodeMessage(MessageId parent, Group group, byte[] body)
 	throws IOException, GeneralSecurityException {
 		return encodeMessage(parent, group, null, null, null, body);
@@ -56,7 +61,8 @@ class MessageEncoderImpl implements MessageEncoder {
 
 		if((author == null) != (authorKey == null))
 			throw new IllegalArgumentException();
-		if((group.getPublicKey() == null) != (groupKey == null))
+		if((group == null || group.getPublicKey() == null) !=
+			(groupKey == null))
 			throw new IllegalArgumentException();
 		if(body.length > Message.MAX_BODY_LENGTH)
 			throw new IllegalArgumentException();
@@ -68,7 +74,8 @@ class MessageEncoderImpl implements MessageEncoder {
 		w.writeUserDefinedTag(Types.MESSAGE);
 		if(parent == null) w.writeNull();
 		else parent.writeTo(w);
-		group.writeTo(w);
+		if(group == null) w.writeNull();
+		else group.writeTo(w);
 		if(author == null) w.writeNull();
 		else author.writeTo(w);
 		w.writeInt64(timestamp);
diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java
index 688056e01930a56c45d2ed1e496ed0bc86594203..abcf512065429758dac2cfc0646d2de99faf3417 100644
--- a/components/net/sf/briar/protocol/MessageReader.java
+++ b/components/net/sf/briar/protocol/MessageReader.java
@@ -12,6 +12,7 @@ import net.sf.briar.api.crypto.KeyParser;
 import net.sf.briar.api.protocol.Author;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.ProtocolConstants;
@@ -57,10 +58,15 @@ class MessageReader implements ObjectReader<Message> {
 			parent = r.readUserDefined(Types.MESSAGE_ID, MessageId.class);
 			r.removeObjectReader(Types.MESSAGE_ID);
 		}
-		// Read the group
-		r.addObjectReader(Types.GROUP, groupReader);
-		Group group = r.readUserDefined(Types.GROUP, Group.class);
-		r.removeObjectReader(Types.GROUP);
+		// Read the group, if there is one
+		Group group = null;
+		if(r.hasNull()) {
+			r.readNull();
+		} else {
+			r.addObjectReader(Types.GROUP, groupReader);
+			group = r.readUserDefined(Types.GROUP, Group.class);
+			r.removeObjectReader(Types.GROUP);
+		}
 		// Read the author, if there is one
 		Author author = null;
 		if(r.hasNull()) {
@@ -85,7 +91,7 @@ class MessageReader implements ObjectReader<Message> {
 		int signedByGroup = (int) counting.getCount();
 		// Read the group's signature, if there is one
 		byte[] groupSig = null;
-		if(group.getPublicKey() == null) r.readNull();
+		if(group == null || group.getPublicKey() == null) r.readNull();
 		else groupSig = r.readBytes(Message.MAX_SIGNATURE_LENGTH);
 		// That's all, folks
 		r.removeConsumer(counting);
@@ -103,7 +109,7 @@ class MessageReader implements ObjectReader<Message> {
 			}
 		}
 		// Verify the group's signature, if there is one
-		if(group.getPublicKey() != null) {
+		if(group != null && group.getPublicKey() != null) {
 			try {
 				PublicKey k = keyParser.parsePublicKey(group.getPublicKey());
 				signature.initVerify(k);
@@ -118,7 +124,7 @@ class MessageReader implements ObjectReader<Message> {
 		messageDigest.update(raw);
 		MessageId id = new MessageId(messageDigest.digest());
 		AuthorId authorId = author == null ? null : author.getId();
-		return new MessageImpl(id, parent, group.getId(), authorId, timestamp,
-				raw);
+		GroupId groupId = group == null ? null : group.getId();
+		return new MessageImpl(id, parent, groupId, authorId, timestamp, raw);
 	}
 }