diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 6b12578c8ce86fa3baf171ca742e1740819d6be8..5dbdd9771386846f1dcc3426e70ab1cdb7366949 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -52,5 +52,9 @@
 			android:name=".android.messages.ReadMessageActivity"
 			android:label="@string/messages_title" >
 		</activity>
+		<activity
+			android:name=".android.messages.WriteMessageActivity"
+			android:label="@string/compose_title" >
+		</activity>
 	</application>
 </manifest>
diff --git a/briar-android/res/drawable-hdpi/social_send_now.png b/briar-android/res/drawable-hdpi/social_send_now.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bdd9585fcfe188ec4a81c81b2861f2be0883499
Binary files /dev/null and b/briar-android/res/drawable-hdpi/social_send_now.png differ
diff --git a/briar-android/res/drawable-mdpi/social_send_now.png b/briar-android/res/drawable-mdpi/social_send_now.png
new file mode 100644
index 0000000000000000000000000000000000000000..515668a6b6c37f0c93f737def79a203e083b7d61
Binary files /dev/null and b/briar-android/res/drawable-mdpi/social_send_now.png differ
diff --git a/briar-android/res/drawable-xhdpi/social_send_now.png b/briar-android/res/drawable-xhdpi/social_send_now.png
new file mode 100644
index 0000000000000000000000000000000000000000..0c870d2ce19d9c4469dccaaeee0d1d635232803f
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/social_send_now.png differ
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index df481efc4dd7d18c1cdef0b7fddb741de288109a..df2deb07c7a3308841e6488e1982b9e7a19ae1b0 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -43,4 +43,5 @@
 	<string name="done_button">Done</string>
 	<string name="messages_title">Messages</string>
 	<string name="compose_button">New Message</string>
+	<string name="compose_title">New Message</string>
 </resources>
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
index c28abac256fb2bc37e7aa1857deb830808942396..f178060fda377a80bceb642c1237064da6f22fe7 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
@@ -165,13 +165,17 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 	}
 
 	public void onClick(View view) {
-		// FIXME: Hook this button up to an activity
+		Intent i = new Intent(this, WriteMessageActivity.class);
+		i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
+		i.putExtra("net.sf.briar.CONTACT_NAME", contactName);
+		startActivity(i);
 	}
 
 	public void onItemClick(AdapterView<?> parent, View view, int position,
 			long id) {
 		PrivateMessageHeader item = adapter.getItem(position);
 		Intent i = new Intent(this, ReadMessageActivity.class);
+		i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
 		i.putExtra("net.sf.briar.CONTACT_NAME", contactName);
 		i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes());
 		i.putExtra("net.sf.briar.CONTENT_TYPE", item.getContentType());
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationItem.java b/briar-android/src/net/sf/briar/android/messages/ConversationItem.java
deleted file mode 100644
index 2640bc105efcf064bd501251b0e8fd1fe81086cb..0000000000000000000000000000000000000000
--- a/briar-android/src/net/sf/briar/android/messages/ConversationItem.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package net.sf.briar.android.messages;
-
-import net.sf.briar.api.db.PrivateMessageHeader;
-import net.sf.briar.api.messaging.MessageId;
-
-class ConversationItem {
-
-	private final PrivateMessageHeader header;
-	private final byte[] body;
-	private final boolean expanded;
-
-	ConversationItem(PrivateMessageHeader header) {
-		this.header = header;
-		body = null;
-		expanded = false;
-	}
-
-	// Collapse an existing item
-	ConversationItem(ConversationItem item) {
-		this.header = item.header;
-		body = null;
-		expanded = false;
-	}
-
-	// Expand an existing item
-	ConversationItem(ConversationItem item, byte[] body) {
-		this.header = item.header;
-		this.body = body;
-		expanded = true;
-	}
-
-	MessageId getId() {
-		return header.getId();
-	}
-
-	String getContentType() {
-		return header.getContentType();
-	}
-
-	String getSubject() {
-		return header.getSubject();
-	}
-
-	long getTimestamp() {
-		return header.getTimestamp();
-	}
-
-	boolean isRead() {
-		return header.isRead();
-	}
-
-	boolean isStarred() {
-		return header.isStarred();
-	}
-
-	boolean isIncoming() {
-		return header.isIncoming();
-	}
-
-	byte[] getBody() {
-		return body;
-	}
-
-	boolean isExpanded() {
-		return expanded;
-	}
-}
diff --git a/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java
index 9a2ec2b492b37c91b661621b2a8a97ba05f4bd30..e998249754d8205e6b36c5911852e6e8cd951298 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java
@@ -18,6 +18,7 @@ import net.sf.briar.R;
 import net.sf.briar.android.BriarActivity;
 import net.sf.briar.android.BriarService;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
+import net.sf.briar.api.ContactId;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
@@ -47,6 +48,8 @@ implements OnClickListener {
 	@Inject private DatabaseComponent db;
 	@Inject @DatabaseExecutor private Executor dbExecutor;
 
+	private ContactId contactId = null;
+	private String contactName = null;
 	private MessageId messageId = null;
 	private boolean starred = false;
 	private ImageButton starButton = null, replyButton = null;
@@ -57,12 +60,15 @@ implements OnClickListener {
 		super.onCreate(null);
 
 		Intent i = getIntent();
-		String contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME");
+		int cid = i.getIntExtra("net.sf.briar.CONTACT_ID", -1);
+		if(cid == -1) throw new IllegalStateException();
+		contactId = new ContactId(cid);
+		contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME");
 		if(contactName == null) throw new IllegalStateException();
 		setTitle(contactName);
-		byte[] id = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
-		if(id == null) throw new IllegalStateException();
-		messageId = new MessageId(id);
+		byte[] mid = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
+		if(mid == null) throw new IllegalStateException();
+		messageId = new MessageId(mid);
 		String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE");
 		if(contentType == null) throw new IllegalStateException();
 		long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
@@ -181,7 +187,6 @@ implements OnClickListener {
 		unbindService(serviceConnection);
 	}
 
-	@Override
 	public void onClick(View view) {
 		if(view == starButton) {
 			final MessageId id = messageId;
@@ -201,7 +206,11 @@ implements OnClickListener {
 				starButton.setImageResource(R.drawable.rating_important);
 			else starButton.setImageResource(R.drawable.rating_not_important);
 		} else if(view == replyButton) {
-			// FIXME: Hook this up to an activity
+			Intent i = new Intent(this, WriteMessageActivity.class);
+			i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
+			i.putExtra("net.sf.briar.CONTACT_NAME", contactName);
+			i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes());
+			startActivity(i);
 		}
 	}
 }
diff --git a/briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..92d0dbfdfffb19e1845e226a7f3ee7246bf14767
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java
@@ -0,0 +1,164 @@
+package net.sf.briar.android.messages;
+
+import static android.view.Gravity.CENTER_VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import net.sf.briar.R;
+import net.sf.briar.android.BriarActivity;
+import net.sf.briar.android.BriarService;
+import net.sf.briar.android.BriarService.BriarServiceConnection;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DatabaseExecutor;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.messaging.Message;
+import net.sf.briar.api.messaging.MessageFactory;
+import net.sf.briar.api.messaging.MessageId;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ScrollView;
+
+import com.google.inject.Inject;
+
+public class WriteMessageActivity extends BriarActivity
+implements OnClickListener {
+
+	private static final Logger LOG =
+			Logger.getLogger(WriteMessageActivity.class.getName());
+
+	private final BriarServiceConnection serviceConnection =
+			new BriarServiceConnection();
+
+	@Inject private BundleEncrypter bundleEncrypter;
+	@Inject private DatabaseComponent db;
+	@Inject @DatabaseExecutor private Executor dbExecutor;
+	@Inject private MessageFactory messageFactory;
+
+	private ContactId contactId = null;
+	private String contactName = null;
+	private MessageId parentId = null;
+	private ImageButton cancelButton = null, sendButton = null;
+	private EditText content = null;
+
+	@Override
+	public void onCreate(Bundle state) {
+		super.onCreate(null);
+
+		Intent i = getIntent();
+		int cid = i.getIntExtra("net.sf.briar.CONTACT_ID", -1);
+		if(cid == -1) throw new IllegalStateException();
+		contactId = new ContactId(cid);
+		contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME");
+		if(contactName == null) throw new IllegalStateException();
+		byte[] pid = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
+		if(pid != null) parentId = new MessageId(pid);
+
+		LinearLayout layout = new LinearLayout(this);
+		layout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+		layout.setOrientation(VERTICAL);
+
+		LinearLayout header = new LinearLayout(this);
+		header.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+		header.setOrientation(HORIZONTAL);
+		header.setGravity(CENTER_VERTICAL);
+
+		cancelButton = new ImageButton(this);
+		cancelButton.setPadding(5, 5, 5, 5);
+		cancelButton.setBackgroundResource(0);
+		cancelButton.setImageResource(R.drawable.navigation_cancel);
+		cancelButton.setOnClickListener(this);
+		header.addView(cancelButton);
+
+		sendButton = new ImageButton(this);
+		sendButton.setPadding(5, 5, 5, 5);
+		sendButton.setBackgroundResource(0);
+		sendButton.setImageResource(R.drawable.social_send_now);
+		sendButton.setOnClickListener(this);
+		header.addView(sendButton);
+		layout.addView(header);
+
+		ScrollView scrollView = new ScrollView(this);
+		content = new EditText(this);
+		content.setPadding(10, 10, 10, 10);
+		if(state != null && bundleEncrypter.decrypt(state)) {
+			Parcelable p = state.getParcelable("net.sf.briar.CONTENT");
+			if(p != null) content.onRestoreInstanceState(p);
+		}
+		scrollView.addView(content);
+		layout.addView(scrollView);
+
+		setContentView(layout);
+
+		// Bind to the service so we can wait for the DB to be opened
+		bindService(new Intent(BriarService.class.getName()),
+				serviceConnection, 0);
+	}
+
+	@Override
+	public void onSaveInstanceState(Bundle state) {
+		Parcelable p = content.onSaveInstanceState();
+		state.putParcelable("net.sf.briar.CONTENT", p);
+		bundleEncrypter.encrypt(state);
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		unbindService(serviceConnection);
+	}
+
+	public void onClick(View view) {
+		if(view == cancelButton) {
+			finish();
+		} else if(view == sendButton) {
+			final Message m;
+			try {
+				byte[] body = content.getText().toString().getBytes("UTF-8");
+				m = messageFactory.createPrivateMessage(parentId,
+						"text/plain", body);
+			} catch(UnsupportedEncodingException e) {
+				throw new RuntimeException(e);
+			} catch(IOException e) {
+				throw new RuntimeException(e);
+			} catch(GeneralSecurityException e) {
+				throw new RuntimeException(e);
+			}
+			final ContactId contactId = this.contactId;
+			dbExecutor.execute(new Runnable() {
+				public void run() {
+					try {
+						serviceConnection.waitForStartup();
+						db.addLocalPrivateMessage(m, contactId);
+					} catch(DbException e) {
+						if(LOG.isLoggable(WARNING))
+							LOG.log(WARNING, e.toString(), e);
+					} catch(InterruptedException e) {
+						if(LOG.isLoggable(INFO))
+							LOG.info("Interrupted while waiting for service");
+						Thread.currentThread().interrupt();
+					}
+				}
+			});
+			finish();
+		}
+	}
+}