diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index c4433c846e9ee5abceef41270ab73d6d9a14b8a8..b07d49a008e3f466338c27ab7dbf942e47338eb3 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -63,8 +63,11 @@ public interface DatabaseComponent {
 	ContactId addContact(Map<String, Map<String, String>> transports,
 			byte[] secret) throws DbException;
 
-	/** Adds a locally generated message to the database. */
-	void addLocallyGeneratedMessage(Message m) throws DbException;
+	/** Adds a locally generated group message to the database. */
+	void addLocalGroupMessage(Message m) throws DbException;
+
+	/** Adds a locally generated private message to the database. */
+	void addLocalPrivateMessage(Message m, ContactId c) throws DbException;
 
 	/**
 	 * Finds any lost batches that were sent to the given contact, and marks any
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index 677864257cb3642e1706cd898b55969b4f2b1701..ae45f60289cc9251773bcaa3e0822ba0ed4ac05e 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -1,6 +1,7 @@
 package net.sf.briar.db;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.logging.Level;
@@ -9,8 +10,8 @@ import java.util.logging.Logger;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DatabaseComponent;
-import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.DatabaseListener;
+import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Message;
@@ -175,8 +176,9 @@ DatabaseCleaner.Callback {
 	 * <p>
 	 * Locking: contacts read, messages write, messageStatuses write.
 	 */
-	protected boolean storeMessage(Txn txn, Message m, ContactId sender)
+	protected boolean storeGroupMessage(Txn txn, Message m, ContactId sender)
 	throws DbException {
+		if(m.getGroup() == null) throw new IllegalArgumentException();
 		boolean added = db.addMessage(txn, m);
 		// Mark the message as seen by the sender
 		MessageId id = m.getId();
@@ -198,6 +200,43 @@ DatabaseCleaner.Callback {
 		return added;
 	}
 
+	protected boolean storeMessages(Txn txn, ContactId c,
+			Collection<Message> messages) throws DbException {
+		boolean anyAdded = false;
+		for(Message m : messages) {
+			if(m.getGroup() == null) {
+				if(storePrivateMessage(txn, m, c, true)) anyAdded = true;
+			} else if(db.containsVisibleSubscription(txn, m.getGroup(), c,
+					m.getTimestamp())) {
+				if(storeGroupMessage(txn, m, c)) anyAdded = true;
+			}
+		}
+		return anyAdded;
+	}
+
+	/**
+	 * 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.
+	 * <p>
+	 * Locking: contacts read, messages write, messageStatuses write.
+	 */
+	protected boolean storePrivateMessage(Txn txn, Message m, ContactId c,
+			boolean incoming) throws DbException {
+		if(m.getGroup() != null) throw new IllegalArgumentException();
+		boolean added = db.addMessage(txn, m);
+		if(!added) return false;
+		MessageId id = m.getId();
+		if(incoming) db.setStatus(txn, c, id, Status.SEEN);
+		else db.setStatus(txn, c, id, Status.NEW);
+		// Count the bytes stored
+		synchronized(spaceLock) {
+			bytesStoredSinceLastCheck += m.getSize();
+		}
+		return true;
+	}
+
 	/**
 	 * Iteratively updates the sendability of a message's ancestors to reflect
 	 * a change in the message's sendability. Returns the number of ancestors
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index f0211d04a8738617c0e0389339196b1b413351c1..b4b3bfeeb9b312b89403c3b4a09090c80d91390c 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -132,7 +132,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		return c;
 	}
 
-	public void addLocallyGeneratedMessage(Message m) throws DbException {
+	public void addLocalGroupMessage(Message m) throws DbException {
 		boolean added = false;
 		waitForPermissionToWrite();
 		contactLock.readLock().lock();
@@ -150,7 +150,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							// predates the subscription
 							if(db.containsSubscription(txn, m.getGroup(),
 									m.getTimestamp())) {
-								added = storeMessage(txn, m, null);
+								added = storeGroupMessage(txn, m, null);
 							}
 							db.commitTransaction(txn);
 						} catch(DbException e) {
@@ -173,6 +173,38 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		if(added) callListeners(Event.MESSAGES_ADDED);
 	}
 
+	public void addLocalPrivateMessage(Message m, ContactId c)
+	throws DbException {
+		boolean added = false;
+		waitForPermissionToWrite();
+		contactLock.readLock().lock();
+		try {
+			if(!containsContact(c)) throw new NoSuchContactException();
+			messageLock.writeLock().lock();
+			try {
+				messageStatusLock.writeLock().lock();
+				try {
+					Txn txn = db.startTransaction();
+					try {
+						added = storePrivateMessage(txn, m, c, false);
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
+					}
+				} finally {
+					messageStatusLock.writeLock().unlock();
+				}
+			} finally {
+				messageLock.writeLock().unlock();
+			}
+		} finally {
+			contactLock.readLock().unlock();
+		}
+		// Call the listeners outside the lock
+		if(added) callListeners(Event.MESSAGES_ADDED);
+	}
+
 	public void findLostBatches(ContactId c) throws DbException {
 		// Find any lost batches that need to be retransmitted
 		Collection<BatchId> lost;
@@ -751,20 +783,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 					try {
 						Txn txn = db.startTransaction();
 						try {
-							int received = 0, stored = 0;
-							for(Message m : b.getMessages()) {
-								received++;
-								if(db.containsVisibleSubscription(txn,
-										m.getGroup(), c, m.getTimestamp())) {
-									if(storeMessage(txn, m, c)) {
-										anyAdded = true;
-										stored++;
-									}
-								}
-							}
-							if(LOG.isLoggable(Level.FINE))
-								LOG.fine("Received " + received
-										+ " messages, stored " + stored);
+							anyAdded = storeMessages(txn, c, b.getMessages());
 							db.addBatchToAck(txn, c, b.getId());
 							db.commitTransaction(txn);
 						} catch(DbException e) {
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index f27f9f86ffcd24de462fd6228805b5ee3549ffc3..4c7728f021db87acbfad11bfa8fa2ff6cb918550 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -109,7 +109,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		return c;
 	}
 
-	public void addLocallyGeneratedMessage(Message m) throws DbException {
+	public void addLocalGroupMessage(Message m) throws DbException {
 		boolean added = false;
 		waitForPermissionToWrite();
 		synchronized(contactLock) {
@@ -123,7 +123,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							// predates the subscription
 							if(db.containsSubscription(txn, m.getGroup(),
 									m.getTimestamp())) {
-								added = storeMessage(txn, m, null);
+								added = storeGroupMessage(txn, m, null);
 								if(!added) {
 									if(LOG.isLoggable(Level.FINE))
 										LOG.fine("Duplicate local message");
@@ -145,6 +145,28 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		if(added) callListeners(Event.MESSAGES_ADDED);
 	}
 
+	public void addLocalPrivateMessage(Message m, ContactId c)
+	throws DbException {
+		boolean added = false;
+		waitForPermissionToWrite();
+		synchronized(contactLock) {
+			synchronized(messageLock) {
+				synchronized(messageStatusLock) {
+					Txn txn = db.startTransaction();
+					try {
+						added = storePrivateMessage(txn, m, c, false);
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
+					}
+				}
+			}
+		}
+		// Call the listeners outside the lock
+		if(added) callListeners(Event.MESSAGES_ADDED);
+	}
+
 	public void findLostBatches(ContactId c) throws DbException {
 		// Find any lost batches that need to be retransmitted
 		Collection<BatchId> lost;
@@ -574,21 +596,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 					synchronized(subscriptionLock) {
 						Txn txn = db.startTransaction();
 						try {
-							int received = 0, stored = 0;
-							for(Message m : b.getMessages()) {
-								received++;
-								GroupId g = m.getGroup();
-								if(db.containsVisibleSubscription(txn, g, c,
-										m.getTimestamp())) {
-									if(storeMessage(txn, m, c)) {
-										anyAdded = true;
-										stored++;
-									}
-								}
-							}
-							if(LOG.isLoggable(Level.FINE))
-								LOG.fine("Received " + received
-										+ " messages, stored " + stored);
+							anyAdded = storeMessages(txn, c, b.getMessages());
 							db.addBatchToAck(txn, c, b.getId());
 							db.commitTransaction(txn);
 						} catch(DbException e) {
diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java
index abcf512065429758dac2cfc0646d2de99faf3417..cdcef062c9b264935b2e521d8b8f881c5e175e76 100644
--- a/components/net/sf/briar/protocol/MessageReader.java
+++ b/components/net/sf/briar/protocol/MessageReader.java
@@ -123,8 +123,8 @@ class MessageReader implements ObjectReader<Message> {
 		messageDigest.reset();
 		messageDigest.update(raw);
 		MessageId id = new MessageId(messageDigest.digest());
-		AuthorId authorId = author == null ? null : author.getId();
 		GroupId groupId = group == null ? null : group.getId();
+		AuthorId authorId = author == null ? null : author.getId();
 		return new MessageImpl(id, parent, groupId, authorId, timestamp, raw);
 	}
 }
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 0fb571e1685cf101b737487c6f6b2e7002245a02..ef0cc020fb708c4cd9fb7d12cca969d2c27fe885 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -376,7 +376,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
-		db.addLocallyGeneratedMessage(message);
+		db.addLocalGroupMessage(message);
 
 		context.assertIsSatisfied();
 	}
@@ -399,7 +399,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
-		db.addLocallyGeneratedMessage(message);
+		db.addLocalGroupMessage(message);
 
 		context.assertIsSatisfied();
 	}
@@ -431,7 +431,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
-		db.addLocallyGeneratedMessage(message);
+		db.addLocalGroupMessage(message);
 
 		context.assertIsSatisfied();
 	}
@@ -467,7 +467,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
-		db.addLocallyGeneratedMessage(message);
+		db.addLocalGroupMessage(message);
 
 		context.assertIsSatisfied();
 	}
@@ -1132,7 +1132,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
 		db.addListener(listener);
-		db.addLocallyGeneratedMessage(message);
+		db.addLocalGroupMessage(message);
 
 		context.assertIsSatisfied();
 	}
@@ -1158,7 +1158,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
 		db.addListener(listener);
-		db.addLocallyGeneratedMessage(message);
+		db.addLocalGroupMessage(message);
 
 		context.assertIsSatisfied();
 	}