diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
index fd6e1786b3..48a4716e46 100644
--- a/res/layout/conversation_activity.xml
+++ b/res/layout/conversation_activity.xml
@@ -20,6 +20,11 @@
                   android:paddingTop="?attr/actionBarSize"
                   android:gravity="bottom">
 
+        
+
         
 
-        
diff --git a/res/layout/reminder_header.xml b/res/layout/reminder_header.xml
index 2d737437e3..1af3d4ed06 100644
--- a/res/layout/reminder_header.xml
+++ b/res/layout/reminder_header.xml
@@ -1,59 +1,62 @@
 
 
-
-    
+
+    
-
-        
-
-        
-
-            
-
-            
-
-        
-
-        
-
-            
-
-          
+                  android:layout_weight="1"
+                  android:layout_margin="10dp"
+                  android:orientation="vertical">
+
+        
+
+        
 
     
+
+    
+
+    
+
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8a4be620d8..ba9d1c988f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1037,12 +1037,20 @@
     
     Your build of Signal has expired!
     Messages will no longer send successfully, please update to the most recent version.
+    UPGRADE
     Use as default SMS app?
     Tap to make Signal your default SMS app.
+    SET
     Import system SMS?
     Tap to copy your phone\'s SMS messages into its encrypted database.
-    Enable Signal messages?
-    Tap for instant delivery, stronger privacy, and no SMS fees.
+    IMPORT
+    Enable Signal?
+    Upgrade your messaging experience.
+    ENABLE
+    Invite to Signal?
+    Take your conversation with %1$s to the next level.
+    INVITE
+    CLOSE
 
     
     You
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 4427988c67..45496a27d8 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -66,6 +66,8 @@ import org.thoughtcrime.securesms.components.AnimatingToggle;
 import org.thoughtcrime.securesms.components.ComposeText;
 import org.thoughtcrime.securesms.components.InputAwareLayout;
 import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
+import org.thoughtcrime.securesms.components.reminder.InviteReminder;
+import org.thoughtcrime.securesms.components.reminder.ReminderView;
 import org.thoughtcrime.securesms.components.SendButton;
 import org.thoughtcrime.securesms.components.camera.HidingImageButton;
 import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer;
@@ -85,6 +87,7 @@ import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
 import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
 import org.thoughtcrime.securesms.database.GroupDatabase;
 import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
+import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
 import org.thoughtcrime.securesms.database.ThreadDatabase;
 import org.thoughtcrime.securesms.mms.AttachmentManager;
 import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
@@ -106,6 +109,7 @@ import org.thoughtcrime.securesms.sms.MessageSender;
 import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
 import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
 import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
+import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
 import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
 import org.thoughtcrime.securesms.util.Dialogs;
 import org.thoughtcrime.securesms.util.DirectoryHelper;
@@ -117,16 +121,17 @@ import org.thoughtcrime.securesms.util.GroupUtil;
 import org.thoughtcrime.securesms.util.MediaUtil;
 import org.thoughtcrime.securesms.util.TextSecurePreferences;
 import org.thoughtcrime.securesms.util.Util;
+import org.thoughtcrime.securesms.util.ViewUtil;
 import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
 import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
 import org.whispersystems.libaxolotl.InvalidMessageException;
+import org.whispersystems.libaxolotl.util.guava.Optional;
 import org.whispersystems.textsecure.api.util.InvalidNumberException;
 
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 import static org.thoughtcrime.securesms.TransportOption.Type;
 import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
@@ -175,6 +180,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   private   InputAwareLayout      container;
   private   View                  composePanel;
   private   View                  composeBubble;
+  private   ReminderView          reminderView;
 
   private   AttachmentTypeSelectorAdapter attachmentAdapter;
   private   AttachmentManager             attachmentManager;
@@ -216,16 +222,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     initializeActionBar();
     initializeViews();
     initializeResources();
-    initializeSecurity(false, false).addListener(new ListenableFuture.Listener() {
+    initializeSecurity(false, false).addListener(new AssertedSuccessListener() {
       @Override
       public void onSuccess(Boolean result) {
         initializeDraft();
       }
-
-      @Override
-      public void onFailure(ExecutionException e) {
-        throw new AssertionError(e);
-      }
     });
   }
 
@@ -241,15 +242,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
 
     setIntent(intent);
     initializeResources();
-    initializeSecurity(false, false).addListener(new ListenableFuture.Listener() {
+    initializeSecurity(false, false).addListener(new AssertedSuccessListener() {
       @Override
       public void onSuccess(Boolean result) {
         initializeDraft();
       }
-      @Override
-      public void onFailure(ExecutionException e) {
-        throw new AssertionError(e);
-      }
     });
 
     if (fragment != null) {
@@ -804,14 +801,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
         if (result.first != currentSecureText || result.second != currentSecureVoice) {
           handleSecurityChange(result.first, result.second);
         }
-
         future.set(true);
+        onSecurityUpdated();
       }
     }.execute(recipients);
 
     return future;
   }
 
+  private void onSecurityUpdated() {
+    updateInviteReminder();
+  }
+
+  private void updateInviteReminder() {
+    if (TextSecurePreferences.isPushRegistered(this) && !isSecureText && recipients.isSingleRecipient()) {
+      new ShowInviteReminderTask().execute(recipients);
+    } else {
+      reminderView.hide();
+    }
+  }
 
   private void initializeMmsEnabledCheck() {
     new AsyncTask() {
@@ -828,21 +836,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   }
 
   private void initializeViews() {
-    titleView      = (ConversationTitleView) getSupportActionBar().getCustomView();
-    buttonToggle   = (AnimatingToggle)       findViewById(R.id.button_toggle);
-    sendButton     = (SendButton)            findViewById(R.id.send_button);
-    attachButton   = (ImageButton)           findViewById(R.id.attach_button);
-    composeText    = (ComposeText)           findViewById(R.id.embedded_text_editor);
-    charactersLeft = (TextView)              findViewById(R.id.space_left);
-    emojiToggle    = (EmojiToggle)           findViewById(R.id.emoji_toggle);
-    emojiDrawer    = (EmojiDrawer)           findViewById(R.id.emoji_drawer);
-    unblockButton  = (Button)                findViewById(R.id.unblock_button);
-    composePanel   =                         findViewById(R.id.bottom_panel);
-    composeBubble  =                         findViewById(R.id.compose_bubble);
-    container      = (InputAwareLayout)      findViewById(R.id.layout_container);
-
-    quickAttachmentDrawer = (QuickAttachmentDrawer) findViewById(R.id.quick_attachment_drawer);
-    quickAttachmentToggle = (HidingImageButton)     findViewById(R.id.quick_attachment_toggle);
+    titleView             = (ConversationTitleView) getSupportActionBar().getCustomView();
+    buttonToggle          = ViewUtil.findById(this, R.id.button_toggle);
+    sendButton            = ViewUtil.findById(this, R.id.send_button);
+    attachButton          = ViewUtil.findById(this, R.id.attach_button);
+    composeText           = ViewUtil.findById(this, R.id.embedded_text_editor);
+    charactersLeft        = ViewUtil.findById(this, R.id.space_left);
+    emojiToggle           = ViewUtil.findById(this, R.id.emoji_toggle);
+    emojiDrawer           = ViewUtil.findById(this, R.id.emoji_drawer);
+    unblockButton         = ViewUtil.findById(this, R.id.unblock_button);
+    composePanel          = ViewUtil.findById(this, R.id.bottom_panel);
+    composeBubble         = ViewUtil.findById(this, R.id.compose_bubble);
+    container             = ViewUtil.findById(this, R.id.layout_container);
+    reminderView          = ViewUtil.findById(this, R.id.reminder);
+    quickAttachmentDrawer = ViewUtil.findById(this, R.id.quick_attachment_drawer);
+    quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
 
     container.addOnKeyboardShownListener(this);
 
@@ -944,6 +952,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
         titleView.setTitle(recipients);
         setBlockedUserState(recipients);
         setActionBarColor(recipients.getColor());
+        updateInviteReminder();
       }
     });
   }
@@ -1441,4 +1450,31 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     updateToggleButtonState();
   }
 
+  private class ShowInviteReminderTask extends AsyncTask> {
+    @Override
+    protected Pair doInBackground(Recipients... recipients) {
+      if (recipients.length != 1 || recipients[0] == null) throw new AssertionError("task needs exactly one Recipients object");
+
+      Optional prefs = DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
+                                                             .getRecipientsPreferences(recipients[0].getIds());
+      return new Pair<>(recipients[0], prefs.isPresent() && prefs.get().hasSeenInviteReminder());
+    }
+
+    @Override
+    protected void onPostExecute(Pair result) {
+      if (!result.second && result.first == recipients) {
+        InviteReminder reminder = new InviteReminder(ConversationActivity.this, result.first);
+        reminder.setOkListener(new OnClickListener() {
+          @Override
+          public void onClick(View v) {
+            handleInviteLink();
+            reminderView.requestDismiss();
+          }
+        });
+        reminderView.showReminder(reminder);
+      } else {
+        reminderView.hide();
+      }
+    }
+  }
 }
diff --git a/src/org/thoughtcrime/securesms/ConversationListFragment.java b/src/org/thoughtcrime/securesms/ConversationListFragment.java
index 007b940c75..65e29ae8ef 100644
--- a/src/org/thoughtcrime/securesms/ConversationListFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationListFragment.java
@@ -33,8 +33,6 @@ import android.support.v7.app.AppCompatActivity;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.RecyclerListener;
-import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -48,12 +46,12 @@ import com.afollestad.materialdialogs.AlertDialogWrapper;
 import com.melnykov.fab.FloatingActionButton;
 
 import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener;
-import org.thoughtcrime.securesms.components.DefaultSmsReminder;
-import org.thoughtcrime.securesms.components.ExpiredBuildReminder;
-import org.thoughtcrime.securesms.components.PushRegistrationReminder;
-import org.thoughtcrime.securesms.components.Reminder;
-import org.thoughtcrime.securesms.components.ReminderView;
-import org.thoughtcrime.securesms.components.SystemSmsImportReminder;
+import org.thoughtcrime.securesms.components.reminder.DefaultSmsReminder;
+import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
+import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
+import org.thoughtcrime.securesms.components.reminder.Reminder;
+import org.thoughtcrime.securesms.components.reminder.ReminderView;
+import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
 import org.thoughtcrime.securesms.crypto.MasterSecret;
 import org.thoughtcrime.securesms.database.DatabaseFactory;
 import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
@@ -137,7 +135,7 @@ public class ConversationListFragment extends Fragment
       @Override protected Optional extends Reminder> doInBackground(Context... params) {
         final Context context = params[0];
         if (ExpiredBuildReminder.isEligible(context)) {
-          return Optional.of(new ExpiredBuildReminder());
+          return Optional.of(new ExpiredBuildReminder(context));
         } else if (DefaultSmsReminder.isEligible(context)) {
           return Optional.of(new DefaultSmsReminder(context));
         } else if (SystemSmsImportReminder.isEligible(context)) {
diff --git a/src/org/thoughtcrime/securesms/components/ComposeText.java b/src/org/thoughtcrime/securesms/components/ComposeText.java
index 08e4624692..ae9b85d175 100644
--- a/src/org/thoughtcrime/securesms/components/ComposeText.java
+++ b/src/org/thoughtcrime/securesms/components/ComposeText.java
@@ -57,6 +57,7 @@ public class ComposeText extends EmojiEditText {
     }
 
     append(invite);
+    setSelection(getText().length());
   }
 
   private boolean isLandscape() {
diff --git a/src/org/thoughtcrime/securesms/components/ExpiredBuildReminder.java b/src/org/thoughtcrime/securesms/components/ExpiredBuildReminder.java
deleted file mode 100644
index 55ded55da2..0000000000
--- a/src/org/thoughtcrime/securesms/components/ExpiredBuildReminder.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.thoughtcrime.securesms.components;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.provider.Telephony;
-import android.util.Log;
-import android.view.View;
-
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.TextSecureExpiredException;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.Util;
-
-public class ExpiredBuildReminder extends Reminder {
-
-  private static final String TAG = ExpiredBuildReminder.class.getSimpleName();
-
-  public ExpiredBuildReminder() {
-    super(R.drawable.ic_warning_dark,
-          R.string.reminder_header_expired_build,
-          R.string.reminder_header_expired_build_details);
-  }
-
-  @Override
-  public boolean isDismissable() {
-    return false;
-  }
-
-  public static boolean isEligible(Context context) {
-    return !Util.isBuildFresh();
-  }
-
-}
diff --git a/src/org/thoughtcrime/securesms/components/Reminder.java b/src/org/thoughtcrime/securesms/components/Reminder.java
deleted file mode 100644
index 090f35d3eb..0000000000
--- a/src/org/thoughtcrime/securesms/components/Reminder.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.thoughtcrime.securesms.components;
-
-import android.content.Context;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-
-import org.thoughtcrime.securesms.R;
-
-public abstract class Reminder {
-  private int             iconResId;
-  private int             titleResId;
-  private int             textResId;
-  private OnClickListener okListener;
-  private OnClickListener cancelListener;
-
-  public Reminder(int iconResId, int titleResId, int textResId) {
-    this.iconResId  = iconResId;
-    this.titleResId = titleResId;
-    this.textResId  = textResId;
-  }
-
-  public int getIconResId() {
-    return iconResId;
-  }
-
-  public int getTitleResId() {
-    return titleResId;
-  }
-
-  public int getTextResId() {
-    return textResId;
-  }
-
-  public OnClickListener getOkListener() {
-    return okListener;
-  }
-
-  public OnClickListener getCancelListener() {
-    return cancelListener;
-  }
-
-  public void setOkListener(OnClickListener okListener) {
-    this.okListener = okListener;
-  }
-
-  public void setCancelListener(OnClickListener cancelListener) {
-    this.cancelListener = cancelListener;
-  }
-
-  public boolean isDismissable() {
-    return true;
-  }
-}
diff --git a/src/org/thoughtcrime/securesms/components/DefaultSmsReminder.java b/src/org/thoughtcrime/securesms/components/reminder/DefaultSmsReminder.java
similarity index 78%
rename from src/org/thoughtcrime/securesms/components/DefaultSmsReminder.java
rename to src/org/thoughtcrime/securesms/components/reminder/DefaultSmsReminder.java
index 6a2a0ea8ba..0035a42190 100644
--- a/src/org/thoughtcrime/securesms/components/DefaultSmsReminder.java
+++ b/src/org/thoughtcrime/securesms/components/reminder/DefaultSmsReminder.java
@@ -1,9 +1,8 @@
-package org.thoughtcrime.securesms.components;
+package org.thoughtcrime.securesms.components.reminder;
 
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.provider.Telephony;
 import android.view.View;
@@ -17,9 +16,9 @@ public class DefaultSmsReminder extends Reminder {
 
   @TargetApi(VERSION_CODES.KITKAT)
   public DefaultSmsReminder(final Context context) {
-    super(R.drawable.sms_selection_icon,
-          R.string.reminder_header_sms_default_title,
-          R.string.reminder_header_sms_default_text);
+    super(context.getString(R.string.reminder_header_sms_default_title),
+          context.getString(R.string.reminder_header_sms_default_text),
+          context.getString(R.string.reminder_header_sms_default_button));
 
     final OnClickListener okListener = new OnClickListener() {
       @Override
@@ -30,14 +29,14 @@ public class DefaultSmsReminder extends Reminder {
         context.startActivity(intent);
       }
     };
-    final OnClickListener cancelListener = new OnClickListener() {
+    final OnClickListener dismissListener = new OnClickListener() {
       @Override
       public void onClick(View v) {
         TextSecurePreferences.setPromptedDefaultSmsProvider(context, true);
       }
     };
     setOkListener(okListener);
-    setCancelListener(cancelListener);
+    setDismissListener(dismissListener);
   }
 
   public static boolean isEligible(Context context) {
diff --git a/src/org/thoughtcrime/securesms/components/reminder/ExpiredBuildReminder.java b/src/org/thoughtcrime/securesms/components/reminder/ExpiredBuildReminder.java
new file mode 100644
index 0000000000..f5a5b0fc9c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/reminder/ExpiredBuildReminder.java
@@ -0,0 +1,39 @@
+package org.thoughtcrime.securesms.components.reminder;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.Util;
+
+public class ExpiredBuildReminder extends Reminder {
+  private static final String TAG = ExpiredBuildReminder.class.getSimpleName();
+
+  public ExpiredBuildReminder(final Context context) {
+    super(context.getString(R.string.reminder_header_expired_build),
+          context.getString(R.string.reminder_header_expired_build_details),
+          context.getString(R.string.reminder_header_expired_build_button));
+    setOkListener(new OnClickListener() {
+      @Override public void onClick(View v) {
+        try {
+          context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + context.getPackageName())));
+        } catch (android.content.ActivityNotFoundException anfe) {
+          context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + context.getPackageName())));
+        }
+      }
+    });
+  }
+
+  @Override
+  public boolean isDismissable() {
+    return false;
+  }
+
+  public static boolean isEligible(Context context) {
+    return !Util.isBuildFresh();
+  }
+
+}
diff --git a/src/org/thoughtcrime/securesms/components/reminder/InviteReminder.java b/src/org/thoughtcrime/securesms/components/reminder/InviteReminder.java
new file mode 100644
index 0000000000..255d944771
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/reminder/InviteReminder.java
@@ -0,0 +1,34 @@
+package org.thoughtcrime.securesms.components.reminder;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.recipients.Recipients;
+
+public class InviteReminder extends Reminder {
+
+  public InviteReminder(final @NonNull Context context,
+                        final @NonNull Recipients recipients)
+  {
+    super(context.getString(R.string.reminder_header_invite_title),
+          context.getString(R.string.reminder_header_invite_text, recipients.toShortString()),
+          context.getString(R.string.reminder_header_invite_button));
+
+    setDismissListener(new OnClickListener() {
+      @Override public void onClick(View v) {
+        new AsyncTask() {
+
+          @Override protected Void doInBackground(Void... params) {
+            DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true);
+            return null;
+          }
+        }.execute();
+      }
+    });
+  }
+}
diff --git a/src/org/thoughtcrime/securesms/components/PushRegistrationReminder.java b/src/org/thoughtcrime/securesms/components/reminder/PushRegistrationReminder.java
similarity index 80%
rename from src/org/thoughtcrime/securesms/components/PushRegistrationReminder.java
rename to src/org/thoughtcrime/securesms/components/reminder/PushRegistrationReminder.java
index 6fbb3ef359..12843f4b1f 100644
--- a/src/org/thoughtcrime/securesms/components/PushRegistrationReminder.java
+++ b/src/org/thoughtcrime/securesms/components/reminder/PushRegistrationReminder.java
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.components;
+package org.thoughtcrime.securesms.components.reminder;
 
 import android.content.Context;
 import android.content.Intent;
@@ -13,9 +13,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
 public class PushRegistrationReminder extends Reminder {
 
   public PushRegistrationReminder(final Context context, final MasterSecret masterSecret) {
-    super(R.drawable.ic_push_registration_reminder,
-          R.string.reminder_header_push_title,
-          R.string.reminder_header_push_text);
+    super(context.getString(R.string.reminder_header_push_title),
+          context.getString(R.string.reminder_header_push_text),
+          context.getString(R.string.reminder_header_push_button));
 
     final OnClickListener okListener = new OnClickListener() {
       @Override
diff --git a/src/org/thoughtcrime/securesms/components/reminder/Reminder.java b/src/org/thoughtcrime/securesms/components/reminder/Reminder.java
new file mode 100644
index 0000000000..652a59cecf
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/reminder/Reminder.java
@@ -0,0 +1,56 @@
+package org.thoughtcrime.securesms.components.reminder;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.view.View.OnClickListener;
+
+public abstract class Reminder {
+  private CharSequence buttonText;
+  private CharSequence title;
+  private CharSequence text;
+
+  private OnClickListener okListener;
+  private OnClickListener dismissListener;
+
+  public Reminder(@NonNull CharSequence title,
+                  @NonNull CharSequence text,
+                  @NonNull CharSequence buttonText)
+  {
+    this.title      = title;
+    this.text       = text;
+    this.buttonText = buttonText;
+  }
+
+  public CharSequence getTitle() {
+    return title;
+  }
+
+  public CharSequence getText() {
+    return text;
+  }
+
+  public CharSequence getButtonText() {
+    return buttonText;
+  }
+
+  public OnClickListener getOkListener() {
+    return okListener;
+  }
+
+  public OnClickListener getDismissListener() {
+    return dismissListener;
+  }
+
+  public void setOkListener(OnClickListener okListener) {
+    this.okListener = okListener;
+  }
+
+  public void setDismissListener(OnClickListener dismissListener) {
+    this.dismissListener = dismissListener;
+  }
+
+  public boolean isDismissable() {
+    return true;
+  }
+}
diff --git a/src/org/thoughtcrime/securesms/components/ReminderView.java b/src/org/thoughtcrime/securesms/components/reminder/ReminderView.java
similarity index 59%
rename from src/org/thoughtcrime/securesms/components/ReminderView.java
rename to src/org/thoughtcrime/securesms/components/reminder/ReminderView.java
index d543d55694..3657f2d0ed 100644
--- a/src/org/thoughtcrime/securesms/components/ReminderView.java
+++ b/src/org/thoughtcrime/securesms/components/reminder/ReminderView.java
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.components;
+package org.thoughtcrime.securesms.components.reminder;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -7,23 +7,21 @@ import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.ViewUtil;
 
 /**
  * View to display actionable reminders to the user
  */
 public class ReminderView extends LinearLayout {
   private ViewGroup   container;
-  private ImageButton cancel;
+  private TextView    acceptButton;
+  private TextView    closeButton;
   private TextView    title;
   private TextView    text;
-  private ImageView   icon;
 
   public ReminderView(Context context) {
     super(context);
@@ -43,35 +41,39 @@ public class ReminderView extends LinearLayout {
 
   private void initialize() {
     LayoutInflater.from(getContext()).inflate(R.layout.reminder_header, this, true);
-    container = (ViewGroup  ) findViewById(R.id.container);
-    cancel    = (ImageButton) findViewById(R.id.cancel);
-    title     = (TextView   ) findViewById(R.id.reminder_title);
-    text      = (TextView   ) findViewById(R.id.reminder_text);
-    icon      = (ImageView  ) findViewById(R.id.icon);
+    container    = ViewUtil.findById(this, R.id.container);
+    acceptButton = ViewUtil.findById(this, R.id.accept);
+    closeButton  = ViewUtil.findById(this, R.id.cancel);
+    title        = ViewUtil.findById(this, R.id.reminder_title);
+    text         = ViewUtil.findById(this, R.id.reminder_text);
   }
 
   public void showReminder(final Reminder reminder) {
-    icon.setImageResource(reminder.getIconResId());
-    title.setText(reminder.getTitleResId());
-    text.setText(reminder.getTextResId());
+    title.setText(reminder.getTitle());
+    text.setText(reminder.getText());
+    acceptButton.setText(reminder.getButtonText());
 
-    this.setOnClickListener(reminder.getOkListener());
+    acceptButton.setOnClickListener(reminder.getOkListener());
 
     if (reminder.isDismissable()) {
-      cancel.setOnClickListener(new OnClickListener() {
+      closeButton.setOnClickListener(new OnClickListener() {
         @Override
         public void onClick(View v) {
           hide();
-          if (reminder.getCancelListener() != null) reminder.getCancelListener().onClick(v);
+          if (reminder.getDismissListener() != null) reminder.getDismissListener().onClick(v);
         }
       });
     } else {
-      cancel.setVisibility(View.GONE);
+      closeButton.setVisibility(View.GONE);
     }
 
     container.setVisibility(View.VISIBLE);
   }
 
+  public void requestDismiss() {
+    closeButton.performClick();
+  }
+
   public void hide() {
     container.setVisibility(View.GONE);
   }
diff --git a/src/org/thoughtcrime/securesms/components/SystemSmsImportReminder.java b/src/org/thoughtcrime/securesms/components/reminder/SystemSmsImportReminder.java
similarity index 84%
rename from src/org/thoughtcrime/securesms/components/SystemSmsImportReminder.java
rename to src/org/thoughtcrime/securesms/components/reminder/SystemSmsImportReminder.java
index 6377162141..56e5d40d49 100644
--- a/src/org/thoughtcrime/securesms/components/SystemSmsImportReminder.java
+++ b/src/org/thoughtcrime/securesms/components/reminder/SystemSmsImportReminder.java
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.components;
+package org.thoughtcrime.securesms.components.reminder;
 
 import android.content.Context;
 import android.content.Intent;
@@ -14,9 +14,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
 public class SystemSmsImportReminder extends Reminder {
 
   public SystemSmsImportReminder(final Context context, final MasterSecret masterSecret) {
-    super(R.drawable.sms_system_import_icon,
-          R.string.reminder_header_sms_import_title,
-          R.string.reminder_header_sms_import_text);
+    super(context.getString(R.string.reminder_header_sms_import_title),
+          context.getString(R.string.reminder_header_sms_import_text),
+          context.getString(R.string.reminder_header_sms_import_button));
 
     final OnClickListener okListener = new OnClickListener() {
       @Override
@@ -42,7 +42,7 @@ public class SystemSmsImportReminder extends Reminder {
       }
     };
     setOkListener(okListener);
-    setCancelListener(cancelListener);
+    setDismissListener(cancelListener);
   }
 
   public static boolean isEligible(Context context) {
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index d160c7243b..66a46ef716 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -66,7 +66,8 @@ public class DatabaseFactory {
   private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19;
   private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20;
   private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21;
-  private static final int DATABASE_VERSION                    = 21;
+  private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22;
+  private static final int DATABASE_VERSION                    = 22;
 
   private static final String DATABASE_NAME    = "messages.db";
   private static final Object lock             = new Object();
@@ -768,6 +769,10 @@ public class DatabaseFactory {
         db.execSQL("CREATE INDEX IF NOT EXISTS mms_thread_date_index ON mms (thread_id, date_received);");
       }
 
+      if (oldVersion < INTRODUCED_INVITE_REMINDERS_VERSION) {
+        db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN seen_invite_reminder INTEGER DEFAULT 0");
+      }
+
       db.setTransactionSuccessful();
       db.endTransaction();
     }
diff --git a/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java
index 19f2630131..02f21f0f37 100644
--- a/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/RecipientPreferenceDatabase.java
@@ -23,14 +23,15 @@ public class RecipientPreferenceDatabase extends Database {
   private static final String TAG = RecipientPreferenceDatabase.class.getSimpleName();
   private static final String RECIPIENT_PREFERENCES_URI = "content://textsecure/recipients/";
 
-  private static final String TABLE_NAME    = "recipient_preferences";
-  private static final String ID            = "_id";
-  private static final String RECIPIENT_IDS = "recipient_ids";
-  private static final String BLOCK         = "block";
-  private static final String NOTIFICATION  = "notification";
-  private static final String VIBRATE       = "vibrate";
-  private static final String MUTE_UNTIL    = "mute_until";
-  private static final String COLOR         = "color";
+  private static final String TABLE_NAME           = "recipient_preferences";
+  private static final String ID                   = "_id";
+  private static final String RECIPIENT_IDS        = "recipient_ids";
+  private static final String BLOCK                = "block";
+  private static final String NOTIFICATION         = "notification";
+  private static final String VIBRATE              = "vibrate";
+  private static final String MUTE_UNTIL           = "mute_until";
+  private static final String COLOR                = "color";
+  private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder";
 
   public enum VibrateState {
     DEFAULT(0), ENABLED(1), DISABLED(2);
@@ -58,7 +59,8 @@ public class RecipientPreferenceDatabase extends Database {
           NOTIFICATION + " TEXT DEFAULT NULL, " +
           VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " +
           MUTE_UNTIL + " INTEGER DEFAULT 0, " +
-          COLOR + " TEXT DEFAULT NULL);";
+          COLOR + " TEXT DEFAULT NULL, " +
+          SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0);";
 
   public RecipientPreferenceDatabase(Context context, SQLiteOpenHelper databaseHelper) {
     super(context, databaseHelper);
@@ -86,12 +88,13 @@ public class RecipientPreferenceDatabase extends Database {
                               null, null, null);
 
       if (cursor != null && cursor.moveToNext()) {
-        boolean blocked         = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1;
-        String  notification    = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION));
-        int     vibrateState    = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE));
-        long    muteUntil       = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
-        String  serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
-        Uri     notificationUri = notification == null ? null : Uri.parse(notification);
+        boolean blocked            = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1;
+        String  notification       = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION));
+        int     vibrateState       = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE));
+        long    muteUntil          = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
+        String  serializedColor    = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
+        Uri     notificationUri    = notification == null ? null : Uri.parse(notification);
+        boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1;
 
         MaterialColor color;
 
@@ -106,7 +109,7 @@ public class RecipientPreferenceDatabase extends Database {
 
         return Optional.of(new RecipientsPreferences(blocked, muteUntil,
                                                      VibrateState.fromId(vibrateState),
-                                                     notificationUri, color));
+                                                     notificationUri, color, seenInviteReminder));
       }
 
       return Optional.absent();
@@ -146,6 +149,12 @@ public class RecipientPreferenceDatabase extends Database {
     updateOrInsert(recipients, values);
   }
 
+  public void setSeenInviteReminder(Recipients recipients, boolean seen) {
+    ContentValues values = new ContentValues(1);
+    values.put(SEEN_INVITE_REMINDER, seen ? 1 : 0);
+    updateOrInsert(recipients, values);
+  }
+
   private void updateOrInsert(Recipients recipients, ContentValues contentValues) {
     SQLiteDatabase database = databaseHelper.getWritableDatabase();
 
@@ -171,17 +180,20 @@ public class RecipientPreferenceDatabase extends Database {
     private final VibrateState  vibrateState;
     private final Uri           notification;
     private final MaterialColor color;
+    private final boolean       seenInviteReminder;
 
     public RecipientsPreferences(boolean blocked, long muteUntil,
                                  @NonNull VibrateState vibrateState,
                                  @Nullable Uri notification,
-                                 @Nullable MaterialColor color)
+                                 @Nullable MaterialColor color,
+                                 boolean seenInviteReminder)
     {
-      this.blocked      = blocked;
-      this.muteUntil    = muteUntil;
-      this.vibrateState = vibrateState;
-      this.notification = notification;
-      this.color        = color;
+      this.blocked            = blocked;
+      this.muteUntil          = muteUntil;
+      this.vibrateState       = vibrateState;
+      this.notification       = notification;
+      this.color              = color;
+      this.seenInviteReminder = seenInviteReminder;
     }
 
     public @Nullable MaterialColor getColor() {
@@ -203,5 +215,9 @@ public class RecipientPreferenceDatabase extends Database {
     public @Nullable Uri getRingtone() {
       return notification;
     }
+
+    public boolean hasSeenInviteReminder() {
+      return seenInviteReminder;
+    }
   }
 }
diff --git a/src/org/thoughtcrime/securesms/util/concurrent/AssertedSuccessListener.java b/src/org/thoughtcrime/securesms/util/concurrent/AssertedSuccessListener.java
new file mode 100644
index 0000000000..1bd4e81248
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/concurrent/AssertedSuccessListener.java
@@ -0,0 +1,12 @@
+package org.thoughtcrime.securesms.util.concurrent;
+
+import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
+
+import java.util.concurrent.ExecutionException;
+
+public abstract class AssertedSuccessListener implements Listener {
+  @Override
+  public void onFailure(ExecutionException e) {
+    throw new AssertionError(e);
+  }
+}
diff --git a/test/androidTestEspresso/java/org/thoughtcrime/securesms/ConversationListActivityTest.java b/test/androidTestEspresso/java/org/thoughtcrime/securesms/ConversationListActivityTest.java
index 292f3e2051..65d94888eb 100644
--- a/test/androidTestEspresso/java/org/thoughtcrime/securesms/ConversationListActivityTest.java
+++ b/test/androidTestEspresso/java/org/thoughtcrime/securesms/ConversationListActivityTest.java
@@ -23,10 +23,10 @@ import android.widget.TextView;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
-import org.thoughtcrime.securesms.components.DefaultSmsReminder;
-import org.thoughtcrime.securesms.components.ExpiredBuildReminder;
-import org.thoughtcrime.securesms.components.PushRegistrationReminder;
-import org.thoughtcrime.securesms.components.SystemSmsImportReminder;
+import org.thoughtcrime.securesms.components.reminder.DefaultSmsReminder;
+import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
+import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
+import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
 import org.thoughtcrime.securesms.util.TextSecurePreferences;
 
 import static android.support.test.espresso.Espresso.onView;