diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
index 4c4ba9b965365bce29cad3c7f88e03d5faeb3fe6..ae3e770a4e944e48fc201490cee7e8965c0876e0 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
@@ -3,16 +3,14 @@ package org.briarproject;
 import net.jodah.concurrentunit.Waiter;
 
 import org.briarproject.api.Bytes;
-import org.briarproject.api.clients.MessageQueueManager;
 import org.briarproject.api.clients.ContactGroupFactory;
+import org.briarproject.api.clients.MessageQueueManager;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.KeyPair;
-import org.briarproject.api.crypto.KeyParser;
-import org.briarproject.api.crypto.PrivateKey;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.db.DatabaseComponent;
@@ -820,12 +818,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// sharer posts into the forum
 			long time = clock.currentTimeMillis();
-			byte[] body = TestUtils.getRandomBytes(42);
-			KeyParser keyParser = cryptoComponent.getSignatureKeyParser();
-			PrivateKey key = keyParser.parsePrivateKey(author0.getPrivateKey());
+			String body = TestUtils.getRandomString(42);
 			ForumPost p = forumPostFactory
 					.createPseudonymousPost(forum0.getId(), time, null, author0,
-							"text/plain", body, key);
+							body);
 			forumManager0.addLocalPost(p);
 
 			// sync forum post
@@ -841,11 +837,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// now invitee creates a post
 			time = clock.currentTimeMillis();
-			body = TestUtils.getRandomBytes(42);
-			key = keyParser.parsePrivateKey(author1.getPrivateKey());
+			body = TestUtils.getRandomString(42);
 			p = forumPostFactory
 					.createPseudonymousPost(forum0.getId(), time, null, author1,
-							"text/plain", body, key);
+							body);
 			forumManager1.addLocalPost(p);
 
 			// sync forum post
@@ -886,11 +881,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// now invitee creates a post
 			time = clock.currentTimeMillis();
-			body = TestUtils.getRandomBytes(42);
-			key = keyParser.parsePrivateKey(author1.getPrivateKey());
+			body = TestUtils.getRandomString(42);
 			p = forumPostFactory
 					.createPseudonymousPost(forum0.getId(), time, null, author1,
-							"text/plain", body, key);
+							body);
 			forumManager1.addLocalPost(p);
 
 			// sync forum post
diff --git a/briar-android-tests/src/test/java/org/briarproject/MessageSizeIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/MessageSizeIntegrationTest.java
index 5e5b877cc86364d8fefdffcefac2c0fd6647426d..45fb9d7237bd38f3dcb8b575d5783c8fb736773c 100644
--- a/briar-android-tests/src/test/java/org/briarproject/MessageSizeIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/MessageSizeIntegrationTest.java
@@ -6,8 +6,8 @@ import org.briarproject.api.crypto.PrivateKey;
 import org.briarproject.api.forum.ForumConstants;
 import org.briarproject.api.forum.ForumPost;
 import org.briarproject.api.forum.ForumPostFactory;
-import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.messaging.MessagingConstants;
 import org.briarproject.api.messaging.PrivateMessage;
 import org.briarproject.api.messaging.PrivateMessageFactory;
@@ -68,17 +68,17 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
 		String authorName = TestUtils.getRandomString(
 				MAX_AUTHOR_NAME_LENGTH);
 		byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
-		Author author = authorFactory.createAuthor(authorName, authorPublic);
+		PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
+		LocalAuthor author = authorFactory
+				.createLocalAuthor(authorName, authorPublic,
+						privateKey.getEncoded());
 		// Create a maximum-length forum post
 		GroupId groupId = new GroupId(TestUtils.getRandomId());
 		long timestamp = Long.MAX_VALUE;
 		MessageId parent = new MessageId(TestUtils.getRandomId());
-		String contentType = TestUtils.getRandomString(
-				ForumConstants.MAX_CONTENT_TYPE_LENGTH);
-		byte[] body = new byte[MAX_FORUM_POST_BODY_LENGTH];
-		PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
+		String body = TestUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH);
 		ForumPost post = forumPostFactory.createPseudonymousPost(groupId,
-				timestamp, parent, author, contentType, body, privateKey);
+				timestamp, parent, author, body);
 		// Check the size of the serialised message
 		int length = post.getMessage().getRaw().length;
 		assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH
diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java
index 32f452401409769f9bf7cb6552d1632273b2294a..e8e23988297ab64f8563efcbc09b17c66834eb6c 100644
--- a/briar-android/src/org/briarproject/android/AndroidComponent.java
+++ b/briar-android/src/org/briarproject/android/AndroidComponent.java
@@ -19,7 +19,6 @@ import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.feed.FeedManager;
 import org.briarproject.api.forum.ForumManager;
-import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.forum.ForumSharingManager;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.IdentityManager;
@@ -35,7 +34,6 @@ import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.messaging.PrivateMessageFactory;
 import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.plugins.PluginManager;
-import org.briarproject.api.privategroup.GroupMessageFactory;
 import org.briarproject.api.privategroup.PrivateGroupManager;
 import org.briarproject.api.settings.SettingsManager;
 import org.briarproject.plugins.AndroidPluginsModule;
@@ -97,16 +95,12 @@ public interface AndroidComponent extends CoreEagerSingletons {
 
 	PrivateGroupManager privateGroupManager();
 
-	GroupMessageFactory groupMessageFactory();
-
 	ForumManager forumManager();
 
 	ForumSharingManager forumSharingManager();
 
 	BlogSharingManager blogSharingManager();
 
-	ForumPostFactory forumPostFactory();
-
 	BlogManager blogManager();
 
 	BlogPostFactory blogPostFactory();
diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
index a3b910390d3b409941ccecbf835f8c210d73b58e..9375d0a35d40ab6b81245b784c4e8f555b4bd6ff 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
@@ -3,13 +3,8 @@ package org.briarproject.android.forum;
 import android.support.annotation.Nullable;
 
 import org.briarproject.android.api.AndroidNotificationManager;
-import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.android.threaded.ThreadListControllerImpl;
-import org.briarproject.api.FormatException;
-import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.CryptoExecutor;
-import org.briarproject.api.crypto.KeyParser;
-import org.briarproject.api.crypto.PrivateKey;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
@@ -18,47 +13,36 @@ import org.briarproject.api.event.ForumPostReceivedEvent;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPost;
-import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.forum.ForumPostHeader;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.util.StringUtils;
 
-import java.security.GeneralSecurityException;
 import java.util.Collection;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.identity.Author.Status.OURSELVES;
-
 public class ForumControllerImpl
-		extends ThreadListControllerImpl<Forum, ForumEntry, ForumPostHeader>
+		extends ThreadListControllerImpl<Forum, ForumEntry, ForumPostHeader, ForumPost>
 		implements ForumController {
 
 	private static final Logger LOG =
 			Logger.getLogger(ForumControllerImpl.class.getName());
 
-	private final ForumPostFactory forumPostFactory;
 	private final ForumManager forumManager;
 
 	@Inject
 	ForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
 			LifecycleManager lifecycleManager,
 			@CryptoExecutor Executor cryptoExecutor,
-			ForumPostFactory forumPostFactory, CryptoComponent crypto,
 			ForumManager forumManager, EventBus eventBus,
-			IdentityManager identityManager,
 			AndroidNotificationManager notificationManager) {
-		super(dbExecutor, lifecycleManager, cryptoExecutor, crypto, eventBus,
-				identityManager, notificationManager);
+		super(dbExecutor, lifecycleManager, cryptoExecutor, eventBus,
+				notificationManager);
 		this.forumManager = forumManager;
-		this.forumPostFactory = forumPostFactory;
 	}
 
 	@Override
@@ -76,7 +60,6 @@ public class ForumControllerImpl
 			if (pe.getGroupId().equals(groupId)) {
 				LOG.info("Forum post received, adding...");
 				final ForumPostHeader fph = pe.getForumPostHeader();
-				updateNewestTimestamp(fph.getTimestamp());
 				listener.runOnUiThreadUnlessDestroyed(new Runnable() {
 					@Override
 					public void run() {
@@ -115,65 +98,15 @@ public class ForumControllerImpl
 	}
 
 	@Override
-	public void send(final String body, @Nullable final MessageId parentId,
-			final ResultExceptionHandler<ForumEntry, DbException> handler) {
-		cryptoExecutor.execute(new Runnable() {
-			@Override
-			public void run() {
-				LOG.info("Create post...");
-				long timestamp = System.currentTimeMillis();
-				timestamp = Math.max(timestamp, newestTimeStamp.get());
-				ForumPost p;
-				try {
-					LocalAuthor a = identityManager.getLocalAuthor();
-					KeyParser keyParser = crypto.getSignatureKeyParser();
-					byte[] k = a.getPrivateKey();
-					PrivateKey authorKey = keyParser.parsePrivateKey(k);
-					byte[] b = StringUtils.toUtf8(body);
-					p = forumPostFactory
-							.createPseudonymousPost(groupId, timestamp,
-									parentId, a, "text/plain", b, authorKey);
-				} catch (GeneralSecurityException | FormatException e) {
-					throw new RuntimeException(e);
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					handler.onException(e);
-					return;
-				}
-				bodyCache.put(p.getMessage().getId(), body);
-				storePost(p, handler);
-			}
-		});
+	protected ForumPost createLocalMessage(GroupId g, String body,
+			@Nullable MessageId parentId) throws DbException {
+		return forumManager.createLocalPost(groupId, body, parentId);
 	}
 
-	private void storePost(final ForumPost p,
-			final ResultExceptionHandler<ForumEntry, DbException> resultHandler) {
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					LOG.info("Store post...");
-					long now = System.currentTimeMillis();
-					forumManager.addLocalPost(p);
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Storing message took " + duration + " ms");
-
-					ForumPostHeader h =
-							new ForumPostHeader(p.getMessage().getId(),
-									p.getParent(),
-									p.getMessage().getTimestamp(),
-									p.getAuthor(), OURSELVES, true);
-
-					resultHandler.onResult(buildItem(h));
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					resultHandler.onException(e);
-				}
-			}
-		});
+	@Override
+	protected ForumPostHeader addLocalMessage(ForumPost p)
+			throws DbException {
+		return forumManager.addLocalPost(p);
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
index 40e4d898cddeacf9905a9f284bca83a380be1928..18a9bb57e357bb6945626a2e7a64f14e6299d0fc 100644
--- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
@@ -2,45 +2,46 @@ package org.briarproject.android.privategroup.conversation;
 
 import android.support.annotation.Nullable;
 
-import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.android.api.AndroidNotificationManager;
 import org.briarproject.android.threaded.ThreadListControllerImpl;
-import org.briarproject.api.FormatException;
+import org.briarproject.api.crypto.CryptoExecutor;
+import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.GroupMessageAddedEvent;
-import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.privategroup.GroupMessage;
-import org.briarproject.api.privategroup.GroupMessageFactory;
 import org.briarproject.api.privategroup.GroupMessageHeader;
 import org.briarproject.api.privategroup.PrivateGroup;
 import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 
-import java.security.GeneralSecurityException;
 import java.util.Collection;
+import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.identity.Author.Status.OURSELVES;
-
 public class GroupControllerImpl
-		extends ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader>
+		extends ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage>
 		implements GroupController {
 
 	private static final Logger LOG =
 			Logger.getLogger(GroupControllerImpl.class.getName());
 
-	@Inject
-	volatile GroupMessageFactory groupMessageFactory;
-	@Inject
-	volatile PrivateGroupManager privateGroupManager;
+	private final PrivateGroupManager privateGroupManager;
 
 	@Inject
-	GroupControllerImpl() {
-		super();
+	GroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
+			LifecycleManager lifecycleManager,
+			@CryptoExecutor Executor cryptoExecutor,
+			PrivateGroupManager privateGroupManager, EventBus eventBus,
+			AndroidNotificationManager notificationManager) {
+		super(dbExecutor, lifecycleManager, cryptoExecutor, eventBus,
+				notificationManager);
+		this.privateGroupManager = privateGroupManager;
 	}
 
 	@Override
@@ -58,7 +59,6 @@ public class GroupControllerImpl
 			if (!pe.isLocal() && pe.getGroupId().equals(groupId)) {
 				LOG.info("Group message received, adding...");
 				final GroupMessageHeader fph = pe.getHeader();
-				updateNewestTimestamp(fph.getTimestamp());
 				listener.runOnUiThreadUnlessDestroyed(new Runnable() {
 					@Override
 					public void run() {
@@ -97,59 +97,15 @@ public class GroupControllerImpl
 	}
 
 	@Override
-	public void send(final String body, @Nullable final MessageId parentId,
-			final ResultExceptionHandler<GroupMessageItem, DbException> handler) {
-		cryptoExecutor.execute(new Runnable() {
-			@Override
-			public void run() {
-				LOG.info("Create message...");
-				long timestamp = System.currentTimeMillis();
-				timestamp = Math.max(timestamp, newestTimeStamp.get());
-				GroupMessage gm;
-				try {
-					LocalAuthor a = identityManager.getLocalAuthor();
-					gm = groupMessageFactory.createGroupMessage(groupId,
-							timestamp, parentId, a, body);
-				} catch (GeneralSecurityException | FormatException e) {
-					throw new RuntimeException(e);
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					handler.onException(e);
-					return;
-				}
-				bodyCache.put(gm.getMessage().getId(), body);
-				storeMessage(gm, handler);
-			}
-		});
+	protected GroupMessage createLocalMessage(GroupId g, String body,
+			@Nullable MessageId parentId) throws DbException {
+		return privateGroupManager.createLocalMessage(groupId, body, parentId);
 	}
 
-	private void storeMessage(final GroupMessage gm,
-			final ResultExceptionHandler<GroupMessageItem, DbException> handler) {
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					LOG.info("Store message...");
-					long now = System.currentTimeMillis();
-					privateGroupManager.addLocalMessage(gm);
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Storing message took " + duration + " ms");
-
-					GroupMessageHeader h = new GroupMessageHeader(groupId,
-							gm.getMessage().getId(), gm.getParent(),
-							gm.getMessage().getTimestamp(), gm.getAuthor(),
-							OURSELVES, true);
-
-					handler.onResult(buildItem(h));
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					handler.onException(e);
-				}
-			}
-		});
+	@Override
+	protected GroupMessageHeader addLocalMessage(GroupMessage message)
+			throws DbException {
+		return privateGroupManager.addLocalMessage(message);
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java
index e60f310c4525e204dce212563b157cb0a7f24ccb..3fbf2bccc6c94217842e60b790dc000cf0b1d236 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java
@@ -2,13 +2,14 @@ package org.briarproject.android.threaded;
 
 import android.app.Activity;
 import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
 
 import org.briarproject.android.api.AndroidNotificationManager;
 import org.briarproject.android.controller.DbControllerImpl;
 import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.api.clients.BaseGroup;
+import org.briarproject.api.clients.BaseMessage;
 import org.briarproject.api.clients.PostHeader;
-import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.CryptoExecutor;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
@@ -16,9 +17,6 @@ import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.GroupRemovedEvent;
-import org.briarproject.api.forum.ForumManager;
-import org.briarproject.api.forum.ForumPostFactory;
-import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
@@ -30,13 +28,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.logging.Logger;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends ThreadItem, H extends PostHeader>
+public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends ThreadItem, H extends PostHeader, M extends BaseMessage>
 		extends DbControllerImpl
 		implements ThreadListController<G, I, H>, EventListener {
 
@@ -44,14 +41,11 @@ public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends Th
 			Logger.getLogger(ThreadListControllerImpl.class.getName());
 
 	protected final Executor cryptoExecutor;
-	protected final CryptoComponent crypto;
-	protected final EventBus eventBus;
-	protected final IdentityManager identityManager;
 	protected final AndroidNotificationManager notificationManager;
+	private final EventBus eventBus;
 
 	protected final Map<MessageId, String> bodyCache =
 			new ConcurrentHashMap<>();
-	protected final AtomicLong newestTimeStamp = new AtomicLong();
 
 	protected volatile GroupId groupId;
 
@@ -59,14 +53,11 @@ public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends Th
 
 	protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
 			LifecycleManager lifecycleManager,
-			@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
-			EventBus eventBus, IdentityManager identityManager,
+			@CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
 			AndroidNotificationManager notificationManager) {
 		super(dbExecutor, lifecycleManager);
 		this.cryptoExecutor = cryptoExecutor;
-		this.crypto = crypto;
 		this.eventBus = eventBus;
-		this.identityManager = identityManager;
 		this.notificationManager = notificationManager;
 	}
 
@@ -164,9 +155,6 @@ public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends Th
 					if (LOG.isLoggable(INFO))
 						LOG.info("Loading headers took " + duration + " ms");
 
-					// Update timestamp of newest item
-					updateNewestTimeStamp(headers);
-
 					// Load bodies
 					now = System.currentTimeMillis();
 					loadBodies(headers);
@@ -259,6 +247,63 @@ public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends Th
 		send(body, null, resultHandler);
 	}
 
+	@Override
+	public void send(final String body, @Nullable final MessageId parentId,
+			final ResultExceptionHandler<I, DbException> handler) {
+		cryptoExecutor.execute(new Runnable() {
+			@Override
+			public void run() {
+				LOG.info("Creating message...");
+				try {
+					M msg = createLocalMessage(groupId, body, parentId);
+					bodyCache.put(msg.getMessage().getId(), body);
+					storePost(msg, handler);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+	/**
+	 * This should only be run from the DbThread.
+	 *
+	 * @throws DbException
+	 */
+	protected abstract M createLocalMessage(GroupId g, String body,
+			@Nullable MessageId parentId) throws DbException;
+
+	private void storePost(final M p,
+			final ResultExceptionHandler<I, DbException> resultHandler) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					LOG.info("Store message...");
+					long now = System.currentTimeMillis();
+					H h = addLocalMessage(p);
+					long duration = System.currentTimeMillis() - now;
+					if (LOG.isLoggable(INFO))
+						LOG.info("Storing message took " + duration + " ms");
+					resultHandler.onResult(buildItem(h));
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					resultHandler.onException(e);
+				}
+			}
+		});
+	}
+
+	/**
+	 * This should only be run from the DbThread.
+	 *
+	 * @throws DbException
+	 */
+	protected abstract H addLocalMessage(M message) throws DbException;
+
 	@Override
 	public void deleteGroupItem(
 			final ResultExceptionHandler<Void, DbException> handler) {
@@ -303,20 +348,6 @@ public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends Th
 	 */
 	protected abstract I buildItem(H header);
 
-	private void updateNewestTimeStamp(Collection<H> headers) {
-		for (H h : headers) {
-			updateNewestTimestamp(h.getTimestamp());
-		}
-	}
-
-	protected void updateNewestTimestamp(long update) {
-		long newest = newestTimeStamp.get();
-		while (newest < update) {
-			if (newestTimeStamp.compareAndSet(newest, update)) return;
-			newest = newestTimeStamp.get();
-		}
-	}
-
 	private void checkGroupId() {
 		if (groupId == null) {
 			throw new IllegalStateException(
diff --git a/briar-api/src/org/briarproject/api/forum/ForumManager.java b/briar-api/src/org/briarproject/api/forum/ForumManager.java
index 8c35d05e368420f38e6356b77728dc3d3e2f39a6..edfd53f5508580907090f0944bc380fea4515651 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumManager.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumManager.java
@@ -6,6 +6,7 @@ import org.briarproject.api.db.Transaction;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
@@ -20,8 +21,12 @@ public interface ForumManager extends MessageTracker {
 	/** Unsubscribes from a forum. */
 	void removeForum(Forum f) throws DbException;
 
+	/** Creates a local forum post. */
+	ForumPost createLocalPost(GroupId groupId, String text,
+			@Nullable MessageId parentId) throws DbException;
+
 	/** Stores a local forum post. */
-	void addLocalPost(ForumPost p) throws DbException;
+	ForumPostHeader addLocalPost(ForumPost p) throws DbException;
 
 	/** Returns the forum with the given ID. */
 	Forum getForum(GroupId g) throws DbException;
diff --git a/briar-api/src/org/briarproject/api/forum/ForumPostFactory.java b/briar-api/src/org/briarproject/api/forum/ForumPostFactory.java
index e05edc8831b3538458c44175e8a005d59d56d878..1d6aa9e76d9379b1aaa78ba28f34a849153d25dc 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumPostFactory.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumPostFactory.java
@@ -1,8 +1,7 @@
 package org.briarproject.api.forum;
 
 import org.briarproject.api.FormatException;
-import org.briarproject.api.crypto.PrivateKey;
-import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 
@@ -15,7 +14,6 @@ public interface ForumPostFactory {
 			throws FormatException;
 
 	ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
-			MessageId parent, Author author, String contentType, byte[] body,
-			PrivateKey privateKey) throws FormatException,
-			GeneralSecurityException;
+			MessageId parent, LocalAuthor author, String body)
+			throws FormatException, GeneralSecurityException;
 }
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
index a7fbd0cb12a5421e04d3c1c37eacebb09bf43c19..07ef1c002217399f893d71e03a6e15d6c776c65e 100644
--- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
@@ -7,6 +7,7 @@ import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
@@ -19,8 +20,12 @@ public interface PrivateGroupManager extends MessageTracker {
 	/** Removes a dissolved private group. */
 	void removePrivateGroup(GroupId g) throws DbException;
 
+	/** Creates a local group message. */
+	GroupMessage createLocalMessage(GroupId groupId, String text,
+			@Nullable MessageId parentId) throws DbException;
+
 	/** Stores (and sends) a local group message. */
-	void addLocalMessage(GroupMessage p) throws DbException;
+	GroupMessageHeader addLocalMessage(GroupMessage p) throws DbException;
 
 	/** Returns the private group with the given ID. */
 	@NotNull
diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
index 77a7712e57ae0b4769e0a4ccee6c560981a08cce..ce47a5c3ee80adc3d6602ff0ec22d459f67a44a0 100644
--- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
@@ -13,19 +13,24 @@ import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumFactory;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPost;
+import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.forum.ForumPostHeader;
 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.identity.LocalAuthor;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
+import org.briarproject.api.system.Clock;
 import org.briarproject.clients.BdfIncomingMessageHook;
 import org.briarproject.util.StringUtils;
+import org.jetbrains.annotations.Nullable;
 
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -47,6 +52,7 @@ import static org.briarproject.api.forum.ForumConstants.KEY_PARENT;
 import static org.briarproject.api.forum.ForumConstants.KEY_PUBLIC_NAME;
 import static org.briarproject.api.forum.ForumConstants.KEY_TIMESTAMP;
 import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
+import static org.briarproject.api.identity.Author.Status.OURSELVES;
 import static org.briarproject.clients.BdfConstants.MSG_KEY_READ;
 
 class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
@@ -57,16 +63,21 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 
 	private final IdentityManager identityManager;
 	private final ForumFactory forumFactory;
+	private final ForumPostFactory forumPostFactory;
+	private final Clock clock;
 	private final List<RemoveForumHook> removeHooks;
 
 	@Inject
 	ForumManagerImpl(DatabaseComponent db, IdentityManager identityManager,
 			ClientHelper clientHelper, MetadataParser metadataParser,
-			ForumFactory forumFactory) {
+			ForumFactory forumFactory, ForumPostFactory forumPostFactory,
+			Clock clock) {
 		super(db, clientHelper, metadataParser);
 
 		this.identityManager = identityManager;
 		this.forumFactory = forumFactory;
+		this.forumPostFactory = forumPostFactory;
+		this.clock = clock;
 		removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
 	}
 
@@ -118,7 +129,38 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 	}
 
 	@Override
-	public void addLocalPost(ForumPost p) throws DbException {
+	public ForumPost createLocalPost(final GroupId groupId,
+			final String body, final @Nullable MessageId parentId)
+			throws DbException {
+
+		LocalAuthor author;
+		GroupCount count;
+		Transaction txn = db.startTransaction(true);
+		try {
+			author = identityManager.getLocalAuthor(txn);
+			count = getGroupCount(txn, groupId);
+			txn.setComplete();
+		} finally {
+			db.endTransaction(txn);
+		}
+		long timestamp = clock.currentTimeMillis();
+		timestamp = Math.max(timestamp, count.getLatestMsgTime());
+
+		ForumPost p;
+		try {
+			p = forumPostFactory
+					.createPseudonymousPost(groupId, timestamp, parentId,
+							author, body);
+		} catch (GeneralSecurityException e) {
+			throw new RuntimeException(e);
+		} catch (FormatException e) {
+			throw new DbException(e);
+		}
+		return p;
+	}
+
+	@Override
+	public ForumPostHeader addLocalPost(ForumPost p) throws DbException {
 		Transaction txn = db.startTransaction(false);
 		try {
 			BdfDictionary meta = new BdfDictionary();
@@ -142,6 +184,8 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 		} finally {
 			db.endTransaction(txn);
 		}
+		return new ForumPostHeader(p.getMessage().getId(), p.getParent(),
+				p.getMessage().getTimestamp(), p.getAuthor(), OURSELVES, true);
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java b/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java
index a6c0b226aea4109724a72ebb36ae02ca6299a937..7c0d1e46ddc88a7fefb999340f96185cc99d22be 100644
--- a/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java
@@ -3,12 +3,13 @@ package org.briarproject.forum;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.KeyParser;
 import org.briarproject.api.crypto.PrivateKey;
 import org.briarproject.api.crypto.Signature;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.forum.ForumPost;
 import org.briarproject.api.forum.ForumPostFactory;
-import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
@@ -49,9 +50,10 @@ class ForumPostFactoryImpl implements ForumPostFactory {
 
 	@Override
 	public ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
-			MessageId parent, Author author, String contentType, byte[] body,
-			PrivateKey privateKey) throws FormatException,
-			GeneralSecurityException {
+			MessageId parent, LocalAuthor author, String bodyStr)
+			throws FormatException, GeneralSecurityException {
+		String contentType = "text/plain";
+		byte[] body = StringUtils.toUtf8(bodyStr);
 		// Validate the arguments
 		if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
 			throw new IllegalArgumentException();
@@ -62,6 +64,10 @@ class ForumPostFactoryImpl implements ForumPostFactory {
 				author.getPublicKey());
 		BdfList signed = BdfList.of(groupId, timestamp, parent, authorList,
 				contentType, body);
+		// Get private key
+		KeyParser keyParser = crypto.getSignatureKeyParser();
+		byte[] k = author.getPrivateKey();
+		PrivateKey privateKey = keyParser.parsePrivateKey(k);
 		// Generate the signature
 		Signature signature = crypto.getSignature();
 		signature.initSign(privateKey);
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
index d9483d33602d7b67dc9955f87be27bf573b4f538..b0ef7e072db3382199f5d99fad503c12f0a7df4f 100644
--- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
@@ -9,7 +9,9 @@ import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.privategroup.GroupMessage;
+import org.briarproject.api.privategroup.GroupMessageFactory;
 import org.briarproject.api.privategroup.GroupMessageHeader;
 import org.briarproject.api.privategroup.PrivateGroup;
 import org.briarproject.api.privategroup.PrivateGroupFactory;
@@ -19,16 +21,21 @@ import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
+import org.briarproject.api.system.Clock;
 import org.briarproject.clients.BdfIncomingMessageHook;
 import org.briarproject.util.StringUtils;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
+import java.security.GeneralSecurityException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
+import static org.briarproject.api.identity.Author.Status.OURSELVES;
+
 public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		PrivateGroupManager {
 
@@ -40,16 +47,21 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 
 	private final IdentityManager identityManager;
 	private final PrivateGroupFactory privateGroupFactory;
+	private final GroupMessageFactory groupMessageFactory;
+	private final Clock clock;
 
 	@Inject
 	PrivateGroupManagerImpl(ClientHelper clientHelper,
 			MetadataParser metadataParser, DatabaseComponent db,
 			IdentityManager identityManager,
-			PrivateGroupFactory privateGroupFactory) {
+			PrivateGroupFactory privateGroupFactory,
+			GroupMessageFactory groupMessageFactory, Clock clock) {
 		super(db, clientHelper, metadataParser);
 
 		this.identityManager = identityManager;
 		this.privateGroupFactory = privateGroupFactory;
+		this.groupMessageFactory = groupMessageFactory;
+		this.clock = clock;
 	}
 
 	@NotNull
@@ -64,7 +76,25 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	}
 
 	@Override
-	public void addLocalMessage(GroupMessage m) throws DbException {
+	public GroupMessage createLocalMessage(GroupId groupId, String body,
+			@Nullable MessageId parentId) throws DbException {
+
+		long timestamp = clock.currentTimeMillis();
+		LocalAuthor author = identityManager.getLocalAuthor();
+		try {
+			return groupMessageFactory
+					.createGroupMessage(groupId, timestamp, parentId, author,
+							body);
+		} catch (FormatException e) {
+			throw new DbException(e);
+		} catch (GeneralSecurityException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	public GroupMessageHeader addLocalMessage(GroupMessage m)
+			throws DbException {
 		Transaction txn = db.startTransaction(false);
 		try {
 			BdfDictionary meta = new BdfDictionary();
@@ -76,6 +106,9 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		} finally {
 			db.endTransaction(txn);
 		}
+		return new GroupMessageHeader(m.getMessage().getGroupId(),
+				m.getMessage().getId(), m.getParent(),
+				m.getMessage().getTimestamp(), m.getAuthor(), OURSELVES, true);
 	}
 
 	@NotNull