diff --git a/briar-api/src/org/briarproject/api/clients/BaseMessage.java b/briar-api/src/org/briarproject/api/clients/BaseMessage.java
index fbfb56337789d1e2cb3dbee512fc5e170b04b84e..ee3c0319f484e0d58889681ed1acd20f30b24d4c 100644
--- a/briar-api/src/org/briarproject/api/clients/BaseMessage.java
+++ b/briar-api/src/org/briarproject/api/clients/BaseMessage.java
@@ -3,7 +3,6 @@ package org.briarproject.api.clients;
 import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.annotation.concurrent.Immutable;
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
index 06f277460e7828d4231bbefc58b4a127e9b7be2c..4de6bbbb0a0430fba36ef09ddf1c2dd0ac610958 100644
--- a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
@@ -5,7 +5,6 @@ import org.briarproject.api.identity.Author;
 import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.annotation.concurrent.Immutable;
@@ -14,16 +13,16 @@ import javax.annotation.concurrent.Immutable;
 @NotNullByDefault
 public class GroupMessage extends BaseMessage {
 
-	private final Author author;
+	private final Author member;
 
 	public GroupMessage(Message message, @Nullable MessageId parent,
-			Author author) {
+			Author member) {
 		super(message, parent);
-		this.author = author;
+		this.member = member;
 	}
 
-	public Author getAuthor() {
-		return author;
+	public Author getMember() {
+		return member;
 	}
 
 }
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java
index 76f46314e96b752f3aba60f3eb69d3f1ee0d3281..26e7ae9c7b73f533b5136fc05a709d28347e171d 100644
--- a/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java
@@ -1,20 +1,58 @@
 package org.briarproject.api.privategroup;
 
-import org.briarproject.api.FormatException;
-import org.briarproject.api.crypto.PrivateKey;
+import org.briarproject.api.crypto.CryptoExecutor;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
-import org.jetbrains.annotations.NotNull;
-
-import java.security.GeneralSecurityException;
+import org.jetbrains.annotations.Nullable;
 
 public interface GroupMessageFactory {
 
-	@NotNull
+	/**
+	 * Creates a new member announcement that contains the joiner's identity
+	 * and is signed by the creator.
+	 * <p>
+	 * When a new member accepts an invitation to the group,
+	 * the creator sends this new member announcement to the group.
+	 *
+	 * @param groupId   The ID of the group the new member joined
+	 * @param timestamp The current timestamp
+	 * @param creator   The creator of the group with {@param groupId}
+	 * @param member    The new member that has just accepted an invitation
+	 */
+	@CryptoExecutor
+	GroupMessage createNewMemberMessage(GroupId groupId, long timestamp,
+			LocalAuthor creator, Author member);
+
+	/**
+	 * Creates a join announcement message
+	 * that depends on a previous new member announcement.
+	 *
+	 * @param groupId     The ID of the Group that is being joined
+	 * @param timestamp   Must be equal to the timestamp of the new member message
+	 * @param member      Our own LocalAuthor
+	 * @param newMemberId The MessageId of the new member message
+	 */
+	@CryptoExecutor
+	GroupMessage createJoinMessage(GroupId groupId, long timestamp,
+			LocalAuthor member, MessageId newMemberId);
+
+	/**
+	 * Creates a group message
+	 *
+	 * @param groupId       The ID of the Group that is posted in
+	 * @param timestamp     Must be greater than the timestamps of the parentId
+	 *                      post, if any, and the member's previous message
+	 * @param parentId      The ID of the message that is replied to
+	 * @param author        The author of the group message
+	 * @param body          The content of the group message
+	 * @param previousMsgId The ID of the author's previous message
+	 *                      in this group
+	 */
+	@CryptoExecutor
 	GroupMessage createGroupMessage(GroupId groupId, long timestamp,
-			MessageId parent, LocalAuthor author, String body)
-			throws FormatException, GeneralSecurityException;
+			@Nullable MessageId parentId, LocalAuthor author, String body,
+			MessageId previousMsgId);
 
 }
diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java
index 271f7712c02c3b24373bddbb715a77e36f490400..4569bc50d5d052f886e609b13f49c7d141ff61a1 100644
--- a/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java
+++ b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java
@@ -3,18 +3,25 @@ package org.briarproject.privategroup;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfList;
+import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.privategroup.GroupMessage;
 import org.briarproject.api.privategroup.GroupMessageFactory;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
-import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.security.GeneralSecurityException;
 
 import javax.inject.Inject;
 
+import static org.briarproject.api.privategroup.MessageType.JOIN;
+import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER;
+import static org.briarproject.api.privategroup.MessageType.POST;
+
+@NotNullByDefault
 class GroupMessageFactoryImpl implements GroupMessageFactory {
 
 	private final ClientHelper clientHelper;
@@ -24,20 +31,78 @@ class GroupMessageFactoryImpl implements GroupMessageFactory {
 		this.clientHelper = clientHelper;
 	}
 
-	@NotNull
 	@Override
-	public GroupMessage createGroupMessage(GroupId groupId, long timestamp,
-			MessageId parent, LocalAuthor author, String body)
-			throws FormatException, GeneralSecurityException {
+	public GroupMessage createNewMemberMessage(GroupId groupId, long timestamp,
+			LocalAuthor creator, Author member) {
+		try {
+			// Generate the signature
+			BdfList toSign = BdfList.of(groupId, timestamp, member.getName(),
+					member.getPublicKey());
+			byte[] signature =
+					clientHelper.sign(toSign, creator.getPrivateKey());
+
+			// Compose the message
+			BdfList body =
+					BdfList.of(NEW_MEMBER.getInt(), member.getName(),
+							member.getPublicKey(), signature);
+			Message m = clientHelper.createMessage(groupId, timestamp, body);
+
+			return new GroupMessage(m, null, member);
+		} catch (GeneralSecurityException e) {
+			throw new RuntimeException(e);
+		} catch (FormatException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	public GroupMessage createJoinMessage(GroupId groupId, long timestamp,
+			LocalAuthor member, MessageId newMemberId) {
+		try {
+			// Generate the signature
+			BdfList toSign = BdfList.of(groupId, timestamp, member.getName(),
+					member.getPublicKey(), newMemberId);
+			byte[] signature =
+					clientHelper.sign(toSign, member.getPrivateKey());
+
+			// Compose the message
+			BdfList body =
+					BdfList.of(JOIN.getInt(), member.getName(),
+							member.getPublicKey(), newMemberId, signature);
+			Message m = clientHelper.createMessage(groupId, timestamp, body);
+
+			return new GroupMessage(m, null, member);
+		} catch (GeneralSecurityException e) {
+			throw new RuntimeException(e);
+		} catch (FormatException e) {
+			throw new RuntimeException(e);
+		}
+	}
 
-		// Generate the signature
-		byte[] sig = clientHelper.sign(new BdfList(), author.getPrivateKey());
+	@Override
+	public GroupMessage createGroupMessage(GroupId groupId, long timestamp,
+			@Nullable MessageId parentId, LocalAuthor author, String content,
+			MessageId previousMsgId) {
+		try {
+			// Generate the signature
+			BdfList toSign = BdfList.of(groupId, timestamp, author.getName(),
+					author.getPublicKey(), parentId, previousMsgId, content);
+			byte[] signature =
+					clientHelper.sign(toSign, author.getPrivateKey());
 
-		// Compose the message
-		Message m =
-				clientHelper.createMessage(groupId, timestamp, new BdfList());
+			// Compose the message
+			BdfList body =
+					BdfList.of(POST.getInt(), author.getName(),
+							author.getPublicKey(), parentId, previousMsgId,
+							content, signature);
+			Message m = clientHelper.createMessage(groupId, timestamp, body);
 
-		return new GroupMessage(m, parent, author);
+			return new GroupMessage(m, parentId, author);
+		} catch (GeneralSecurityException e) {
+			throw new RuntimeException(e);
+		} catch (FormatException e) {
+			throw new RuntimeException(e);
+		}
 	}
 
 }