diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index 90cf59649be14ff38d0237e7c6d4381336e6db65..52dff78ec10c4ff8c47161b06368aec6d366b113 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -14,6 +14,7 @@ dependencies {
 	// This shouldn't be necessary; per section 23.4.4 of the Gradle docs:
 	// "file dependencies are included in transitive project dependencies within the same build".
 	compile files('../briar-core/libs/jsocks.jar')
+
 	compile "com.android.support:support-v4:$supportVersion"
 	compile("com.android.support:appcompat-v7:$supportVersion") {
 		exclude module: 'support-v4'
diff --git a/briar-android/proguard-rules.txt b/briar-android/proguard-rules.txt
index 3f11de5e3bc4635ddd3fbc97833267f45188b5b9..d570d88c69913b9425a9fa9c486357c268afb9fd 100644
--- a/briar-android/proguard-rules.txt
+++ b/briar-android/proguard-rules.txt
@@ -59,4 +59,14 @@
 -dontnote android.support.**
 -dontnote dagger.**
 -dontwarn dagger.**
--dontwarn com.google.common.**
\ No newline at end of file
+-dontwarn com.google.common.**
+
+
+# RSS libraries
+-keep class com.rometools.rome.feed.synd.impl.** { *;}
+-keep class com.rometools.rome.io.impl.** { *;}
+-dontwarn javax.xml.stream.**
+-dontwarn org.jaxen.**
+-dontwarn java.nio.**
+-dontwarn org.codehaus.mojo.animal_sniffer.**
+-dontwarn org.slf4j.impl.**
diff --git a/briar-api/src/org/briarproject/api/feed/Feed.java b/briar-api/src/org/briarproject/api/feed/Feed.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f6af8a2d1a36b1e8ad64f148018040472ca67ef
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/feed/Feed.java
@@ -0,0 +1,89 @@
+package org.briarproject.api.feed;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfEntry;
+import org.briarproject.api.sync.GroupId;
+import org.jetbrains.annotations.Nullable;
+
+import static org.briarproject.api.feed.FeedConstants.KEY_BLOG_GROUP_ID;
+import static org.briarproject.api.feed.FeedConstants.KEY_FEED_ADDED;
+import static org.briarproject.api.feed.FeedConstants.KEY_FEED_AUTHOR;
+import static org.briarproject.api.feed.FeedConstants.KEY_FEED_DESC;
+import static org.briarproject.api.feed.FeedConstants.KEY_FEED_TITLE;
+import static org.briarproject.api.feed.FeedConstants.KEY_FEED_UPDATED;
+import static org.briarproject.api.feed.FeedConstants.KEY_FEED_URL;
+
+public class Feed {
+
+	final private String url;
+	final private GroupId blogId;
+	final private String title, description, author;
+	final private long added, updated;
+
+	public Feed(String url, GroupId blogId, @Nullable String title,
+			@Nullable String description, @Nullable String author,
+			long added, long updated) {
+
+		this.url = url;
+		this.blogId = blogId;
+		this.title = title;
+		this.description = description;
+		this.author = author;
+		this.added = added;
+		this.updated = updated;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public GroupId getBlogId() {
+		return blogId;
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = BdfDictionary.of(
+				new BdfEntry(KEY_FEED_URL, url),
+				new BdfEntry(KEY_BLOG_GROUP_ID, blogId.getBytes()),
+				new BdfEntry(KEY_FEED_ADDED, added),
+				new BdfEntry(KEY_FEED_UPDATED, updated)
+		);
+		if (title != null) d.put(KEY_FEED_TITLE, title);
+		if (description != null) d.put(KEY_FEED_DESC, description);
+		if (author != null) d.put(KEY_FEED_AUTHOR, author);
+		return d;
+	}
+
+	public static Feed from(BdfDictionary d) throws FormatException {
+		String url = d.getString(KEY_FEED_URL);
+		GroupId blogId = new GroupId(d.getRaw(KEY_BLOG_GROUP_ID));
+		String title = d.getOptionalString(KEY_FEED_TITLE);
+		String desc = d.getOptionalString(KEY_FEED_DESC);
+		String author = d.getOptionalString(KEY_FEED_AUTHOR);
+		long added = d.getLong(KEY_FEED_ADDED, 0L);
+		long updated = d.getLong(KEY_FEED_UPDATED, 0L);
+		return new Feed(url, blogId, title, desc, author, added, updated);
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public String getAuthor() {
+		return author;
+	}
+
+	public long getAdded() {
+		return added;
+	}
+
+	public long getUpdated() {
+		return updated;
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/feed/FeedConstants.java b/briar-api/src/org/briarproject/api/feed/FeedConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a38c979f62b02535dbb77bf0002f75c6937afcb
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/feed/FeedConstants.java
@@ -0,0 +1,21 @@
+package org.briarproject.api.feed;
+
+public interface FeedConstants {
+
+	/* delay after start before fetching feed, in minutes */
+	int FETCH_DELAY_INITIAL = 1;
+
+	/* the interval the feed should be fetched, in minutes */
+	int FETCH_INTERVAL = 30;
+
+	// group metadata keys
+	String KEY_FEEDS = "feeds";
+	String KEY_FEED_URL = "feedURL";
+	String KEY_BLOG_GROUP_ID = "blogGroupId";
+	String KEY_FEED_TITLE = "feedTitle";
+	String KEY_FEED_DESC = "feedDesc";
+	String KEY_FEED_AUTHOR = "feedAuthor";
+	String KEY_FEED_ADDED = "feedAdded";
+	String KEY_FEED_UPDATED = "feedUpdated";
+
+}
diff --git a/briar-api/src/org/briarproject/api/feed/FeedManager.java b/briar-api/src/org/briarproject/api/feed/FeedManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..2edb0d9be3cbbc96a485968517e3d8988b22cb3e
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/feed/FeedManager.java
@@ -0,0 +1,24 @@
+package org.briarproject.api.feed;
+
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface FeedManager {
+
+	/** Returns the unique ID of the client. */
+	ClientId getClientId();
+
+	/** Adds a RSS feed. */
+	void addFeed(String url, GroupId g) throws DbException, IOException;
+
+	/** Removes a RSS feed. */
+	void removeFeed(String url) throws DbException;
+
+	/** Gets a list of all added RSS feeds */
+	List<Feed> getFeeds() throws DbException;
+
+}
diff --git a/briar-core/build.gradle b/briar-core/build.gradle
index 711a6b65619926738209bb2526169c3e6f5287e9..ae5071be7f7c59c2d4ac7cb348dda069c8aa9db0 100644
--- a/briar-core/build.gradle
+++ b/briar-core/build.gradle
@@ -9,12 +9,22 @@ dependencies {
 	compile fileTree(dir: 'libs', include: '*.jar')
 	compile "com.madgag.spongycastle:core:1.54.0.0"
 	compile "com.h2database:h2:1.4.190"
+	compile 'com.rometools:rome:1.7.0'
+	compile 'org.jdom:jdom2:2.0.6'
+	compile 'org.slf4j:slf4j-api:1.7.21'
+	compile 'com.squareup.okhttp3:okhttp:3.3.1'
 }
 
 dependencyVerification {
 	verify = [
 			'com.madgag.spongycastle:core:1e7fa4b19ccccd1011364ab838d0b4702470c178bbbdd94c5c90b2d4d749ea1e',
 			'com.h2database:h2:23ba495a07bbbb3bd6c3084d10a96dad7a23741b8b6d64b213459a784195a98c',
+			'com.rometools:rome:3096b7a36c0e54f59b8193c431d28494c6bfa85c72ef3c5f341cdf09eae815e6',
+			'org.jdom:jdom2:1345f11ba606d15603d6740551a8c21947c0215640770ec67271fe78bea97cf5',
+			'org.slf4j:slf4j-api:1d5aeb6bd98b0fdd151269eae941c05f6468a791ea0f1e68d8e7fe518af3e7df',
+			'com.squareup.okhttp3:okhttp:a47f4efa166551cd5acc04f1071d82dafbf05638c21f9ca13068bc6633e3bff6',
+			'com.rometools:rome-utils:2be18a1edc601c31fe49c2000bb5484dd75182309270c2a2561d71888d81587a',
+			'com.squareup.okio:okio:5cfea5afe6c6e441a4dbf6053a07a733b1249d1009382eb44ac2255ccedd0c15',
 	]
 }
 
diff --git a/briar-core/src/org/briarproject/CoreEagerSingletons.java b/briar-core/src/org/briarproject/CoreEagerSingletons.java
index f0899dc2df78e2bc5f827910cc61dfb261b7c502..4f1787c2b4317402540ec97ace9df11f83425a30 100644
--- a/briar-core/src/org/briarproject/CoreEagerSingletons.java
+++ b/briar-core/src/org/briarproject/CoreEagerSingletons.java
@@ -4,6 +4,7 @@ import org.briarproject.blogs.BlogsModule;
 import org.briarproject.contact.ContactModule;
 import org.briarproject.crypto.CryptoModule;
 import org.briarproject.db.DatabaseExecutorModule;
+import org.briarproject.feed.FeedModule;
 import org.briarproject.forum.ForumModule;
 import org.briarproject.identity.IdentityModule;
 import org.briarproject.introduction.IntroductionModule;
@@ -47,4 +48,6 @@ public interface CoreEagerSingletons {
 	void inject(SystemModule.EagerSingletons init);
 
 	void inject(TransportModule.EagerSingletons init);
+
+	void inject(FeedModule.EagerSingletons init);
 }
diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java
index d5db624408eafc41372ee6c3e366aa015d073dc6..d865d6e43a25eebcf6ffa6198794703ab584be23 100644
--- a/briar-core/src/org/briarproject/CoreModule.java
+++ b/briar-core/src/org/briarproject/CoreModule.java
@@ -8,6 +8,7 @@ import org.briarproject.data.DataModule;
 import org.briarproject.db.DatabaseExecutorModule;
 import org.briarproject.db.DatabaseModule;
 import org.briarproject.event.EventModule;
+import org.briarproject.feed.FeedModule;
 import org.briarproject.forum.ForumModule;
 import org.briarproject.identity.IdentityModule;
 import org.briarproject.introduction.IntroductionModule;
@@ -51,7 +52,8 @@ import dagger.Module;
 		SharingModule.class,
 		SyncModule.class,
 		SystemModule.class,
-		TransportModule.class
+		TransportModule.class,
+		FeedModule.class
 })
 public class CoreModule {
 
@@ -71,5 +73,6 @@ public class CoreModule {
 		c.inject(new SystemModule.EagerSingletons());
 		c.inject(new TransportModule.EagerSingletons());
 		c.inject(new IntroductionModule.EagerSingletons());
+		c.inject(new FeedModule.EagerSingletons());
 	}
 }
diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..40a7a5a2f2ffca596019d9922e7a88b604543a96
--- /dev/null
+++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
@@ -0,0 +1,304 @@
+package org.briarproject.feed;
+
+import com.rometools.rome.feed.synd.SyndFeed;
+import com.rometools.rome.io.FeedException;
+import com.rometools.rome.io.SyndFeedInput;
+import com.rometools.rome.io.XmlReader;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.clients.Client;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfEntry;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+import org.briarproject.api.feed.Feed;
+import org.briarproject.api.feed.FeedManager;
+import org.briarproject.api.lifecycle.IoExecutor;
+import org.briarproject.api.lifecycle.Service;
+import org.briarproject.api.lifecycle.ServiceException;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.util.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.feed.FeedConstants.FETCH_DELAY_INITIAL;
+import static org.briarproject.api.feed.FeedConstants.FETCH_INTERVAL;
+import static org.briarproject.api.feed.FeedConstants.KEY_FEEDS;
+
+class FeedManagerImpl implements FeedManager, Service, Client {
+
+	private static final Logger LOG =
+			Logger.getLogger(FeedManagerImpl.class.getName());
+
+	private static final ClientId CLIENT_ID =
+			new ClientId(StringUtils.fromHexString(
+					"466565644d616e6167657202fb797097"
+							+ "255af837abbf8c16e250b3c2ccc286eb"));
+
+	private final ScheduledExecutorService feedExecutor;
+	private final Executor ioExecutor;
+	private final DatabaseComponent db;
+	private final PrivateGroupFactory privateGroupFactory;
+	private final ClientHelper clientHelper;
+	private final BlogManager blogManager;
+
+	@Inject
+	FeedManagerImpl(ScheduledExecutorService feedExecutor,
+			@IoExecutor Executor ioExecutor, DatabaseComponent db,
+			PrivateGroupFactory privateGroupFactory, ClientHelper clientHelper,
+			BlogManager blogManager) {
+
+		this.feedExecutor = feedExecutor;
+		this.ioExecutor = ioExecutor;
+		this.db = db;
+		this.privateGroupFactory = privateGroupFactory;
+		this.clientHelper = clientHelper;
+		this.blogManager = blogManager;
+	}
+
+	@Override
+	public ClientId getClientId() {
+		return CLIENT_ID;
+	}
+
+	@Override
+	public void startService() throws ServiceException {
+		Runnable fetcher = new Runnable() {
+			public void run() {
+				ioExecutor.execute(new Runnable() {
+					@Override
+					public void run() {
+						fetchFeeds();
+					}
+				});
+			}
+		};
+		feedExecutor.scheduleWithFixedDelay(fetcher, FETCH_DELAY_INITIAL,
+				FETCH_INTERVAL, MINUTES);
+	}
+
+	@Override
+	public void stopService() throws ServiceException {
+		// feedExecutor will be stopped by LifecycleManager
+	}
+
+	@Override
+	public void createLocalState(Transaction txn) throws DbException {
+		Group g = getLocalGroup();
+		// Return if we've already set the local group up
+		if (db.containsGroup(txn, g.getId())) return;
+
+		// Store the group
+		db.addGroup(txn, g);
+
+		// Add initial metadata
+		List<Feed> feeds = new ArrayList<Feed>(0);
+		storeFeeds(txn, feeds);
+	}
+
+	@Override
+	public void addFeed(String url, GroupId g) throws DbException, IOException {
+		Feed feed;
+		try {
+			SyndFeed f = getSyndFeed(getFeedInputStream(url));
+			String title = f.getTitle();
+			String description = f.getDescription();
+			String author = f.getAuthor();
+			long added = System.currentTimeMillis();
+			feed = new Feed(url, g, title, description, author, added, added);
+		} catch (FeedException e) {
+			throw new IOException(e);
+		}
+
+		Transaction txn = db.startTransaction(false);
+		try {
+			List<Feed> feeds = getFeeds(txn);
+			feeds.add(feed);
+			storeFeeds(feeds);
+		} finally {
+			db.endTransaction(txn);
+		}
+	}
+
+	@Override
+	public void removeFeed(String url) throws DbException {
+		Transaction txn = db.startTransaction(false);
+		try {
+			List<Feed> feeds = getFeeds(txn);
+			boolean found = false;
+			for (Feed feed : feeds) {
+				if (feed.getUrl().equals(url)) {
+					found = true;
+					feeds.remove(feed);
+				}
+			}
+			if (!found) throw new DbException();
+			storeFeeds(txn, feeds);
+		} finally {
+			db.endTransaction(txn);
+		}
+	}
+
+	@Override
+	public List<Feed> getFeeds() throws DbException {
+		List<Feed> feeds;
+		Transaction txn = db.startTransaction(true);
+		try {
+			feeds = getFeeds(txn);
+			txn.setComplete();
+		} finally {
+			db.endTransaction(txn);
+		}
+		return feeds;
+	}
+
+	private List<Feed> getFeeds(Transaction txn) throws DbException {
+		List<Feed> feeds = new ArrayList<Feed>();
+		Group g = getLocalGroup();
+		try {
+			BdfDictionary d =
+					clientHelper.getGroupMetadataAsDictionary(txn, g.getId());
+			for (Object object : d.getList(KEY_FEEDS)) {
+				if (!(object instanceof BdfDictionary))
+					throw new FormatException();
+				feeds.add(Feed.from((BdfDictionary) object));
+			}
+		} catch (FormatException e) {
+			throw new DbException(e);
+		}
+		return feeds;
+	}
+
+	private void storeFeeds(Transaction txn, List<Feed> feeds)
+			throws DbException {
+
+		BdfList feedList = new BdfList();
+		for (Feed feed : feeds) {
+			feedList.add(feed.toBdfDictionary());
+		}
+		BdfDictionary gm = BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
+		try {
+			if (txn == null) {
+				clientHelper.mergeGroupMetadata(getLocalGroup().getId(), gm);
+			} else {
+				clientHelper
+						.mergeGroupMetadata(txn, getLocalGroup().getId(), gm);
+			}
+		} catch (FormatException e) {
+			throw new DbException(e);
+		}
+	}
+
+	private void storeFeeds(List<Feed> feeds) throws DbException {
+		storeFeeds(null, feeds);
+	}
+
+	private void fetchFeeds() {
+		// Get current feeds
+		List<Feed> feeds;
+		try {
+			feeds = getFeeds();
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING))
+				LOG.log(WARNING, e.toString(), e);
+			return;
+		}
+
+		// Fetch and update all feeds
+		List<Feed> newFeeds = new ArrayList<Feed>(feeds.size());
+		for (Feed feed : feeds) {
+			newFeeds.add(fetchFeed(feed));
+		}
+
+		// Store updated feeds
+		try {
+			storeFeeds(newFeeds);
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING))
+				LOG.log(WARNING, e.toString(), e);
+		}
+	}
+
+	private Feed fetchFeed(Feed feed) {
+		LOG.info("Updating RSS feeds...");
+		String title, description, author;
+		long updated = System.currentTimeMillis();
+		try {
+			SyndFeed f = getSyndFeed(getFeedInputStream(feed.getUrl()));
+			title = f.getTitle();
+			description = f.getDescription();
+			author = f.getAuthor();
+
+			// TODO keep track of which entries have been seen (#485)
+			// TODO Pass any new entries down the pipeline to be posted (#486)
+		} catch (FeedException e) {
+			if (LOG.isLoggable(WARNING))
+				LOG.log(WARNING, e.toString(), e);
+			return feed;
+		} catch (IOException e) {
+			if (LOG.isLoggable(WARNING))
+				LOG.log(WARNING, e.toString(), e);
+			return feed;
+		}
+		return new Feed(feed.getUrl(), feed.getBlogId(), title, description,
+				author, feed.getAdded(), updated);
+	}
+
+	private InputStream getFeedInputStream(String url) throws IOException {
+		// Set proxy
+		// TODO verify and use local Tor proxy address/port
+		String proxyHost = "localhost";
+		int proxyPort = 59050;
+		Proxy proxy = new Proxy(Proxy.Type.HTTP,
+				new InetSocketAddress(proxyHost, proxyPort));
+
+		// Build HTTP Client
+		OkHttpClient client = new OkHttpClient.Builder()
+//				.proxy(proxy)
+				.build();
+
+		// Build Request
+		Request request = new Request.Builder()
+				.url(url)
+				.build();
+
+		// Execute Request
+		Response response = client.newCall(request).execute();
+		return response.body().byteStream();
+	}
+
+	private SyndFeed getSyndFeed(InputStream stream)
+			throws IOException, FeedException {
+
+		SyndFeedInput input = new SyndFeedInput();
+		return input.build(new XmlReader(stream));
+	}
+
+	private Group getLocalGroup() {
+		return privateGroupFactory.createLocalGroup(getClientId());
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/feed/FeedModule.java b/briar-core/src/org/briarproject/feed/FeedModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..aad3bab22fb0b2d43d67c50663905a1b8c68beb8
--- /dev/null
+++ b/briar-core/src/org/briarproject/feed/FeedModule.java
@@ -0,0 +1,30 @@
+package org.briarproject.feed;
+
+import org.briarproject.api.feed.FeedManager;
+import org.briarproject.api.lifecycle.LifecycleManager;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class FeedModule {
+
+	public static class EagerSingletons {
+		@Inject
+		FeedManager feedManager;
+	}
+
+	@Provides
+	@Singleton
+	FeedManager provideFeedManager(FeedManagerImpl feedManager,
+			LifecycleManager lifecycleManager) {
+
+		lifecycleManager.registerClient(feedManager);
+		lifecycleManager.registerService(feedManager);
+		return feedManager;
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index e8b9b129d79120db7198b0dbcdad851d838d22bb..eaefc8e5f2f54ee71f509ea9e504bfc0a294b870 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -138,6 +138,7 @@ class PluginManagerImpl implements PluginManager, Service {
 		}
 		// Wait for all the plugins to stop
 		try {
+			LOG.info("Waiting for all the plugins to stop");
 			stopLatch.await();
 		} catch (InterruptedException e) {
 			throw new ServiceException(e);
@@ -227,6 +228,8 @@ class PluginManagerImpl implements PluginManager, Service {
 
 		@Override
 		public void run() {
+			if (LOG.isLoggable(INFO))
+				LOG.info("Trying to stop plugin " + plugin.getId());
 			try {
 				// Wait for the plugin to finish starting
 				startLatch.await();