diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index 6c631e9a..828f6551 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -19,6 +19,10 @@ class ApplicationMailer < ActionMailer::Base
         when MAILBOXER_CODE_INVITE_TO_EDIT
           user_map = notification.notified_object
           MapMailer.invite_to_edit(user_map)
+        when MAILBOXER_CODE_MAP_MESSAGE
+        when MAILBOXER_CODE_MAP_STARRED
+        when MAILBOXER_CODE_TOPIC_ADDED_TO_MAP
+        when MAILBOXER_CODE_TOPIC_CONNECTED
       end
     end
   end
diff --git a/app/mailers/map_mailer.rb b/app/mailers/map_mailer.rb
index c14e9c65..581d6223 100644
--- a/app/mailers/map_mailer.rb
+++ b/app/mailers/map_mailer.rb
@@ -5,18 +5,18 @@ class MapMailer < ApplicationMailer
   def access_request(request)
     @request = request
     @map = request.map
-    mail(to: @map.user.email, subject: request.requested_text)
+    mail(to: @map.user.email, subject: NotificationService.get_subject_for_event(@map, 'access_request', request))
   end
 
   def access_approved(request)
     @request = request
     @map = request.map
-    mail(to: request.user, subject: request.approved_text)
+    mail(to: request.user, subject: NotificationService.get_subject_for_event(@map, 'access_approved', request))
   end
 
   def invite_to_edit(user_map)
     @inviter = user_map.map.user
     @map = user_map.map
-    mail(to: user_map.user.email, subject: @map.invited_text)
+    mail(to: user_map.user.email, subject: NotificationService.get_subject_for_event(@map, 'invite_to_edit', user_map))
   end
 end
diff --git a/app/models/access_request.rb b/app/models/access_request.rb
index 5c39b174..a26146ff 100644
--- a/app/models/access_request.rb
+++ b/app/models/access_request.rb
@@ -27,14 +27,6 @@ class AccessRequest < ApplicationRecord
       Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
     end
   end
-
-  def requested_text
-    map.name + ' - request to edit'
-  end
-
-  def approved_text
-    map.name + ' - access approved'
-  end
   
   protected
   
diff --git a/app/models/events/topic_updated.rb b/app/models/events/topic_updated.rb
index d52a4298..090cfcda 100644
--- a/app/models/events/topic_updated.rb
+++ b/app/models/events/topic_updated.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 module Events
   class TopicUpdated < Event
-    # after_create :notify_users!
+    #after_create :notify_users!
 
     def self.publish!(topic, user, meta)
       create!(kind: 'topic_updated',
@@ -9,5 +9,9 @@ module Events
               user: user,
               meta: meta)
     end
+    
+    def notify_users!
+      NotificationService.notify_followers(eventable, 'topic_updated', self)
+    end
   end
 end
diff --git a/app/models/follow.rb b/app/models/follow.rb
new file mode 100644
index 00000000..76c9b300
--- /dev/null
+++ b/app/models/follow.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+class Follow < ApplicationRecord
+
+  belongs_to :user
+  belongs_to :followed, polymorphic: true
+  has_one    :follow_reason, dependent: :destroy
+  has_one    :follow_type, dependent: :destroy
+  
+  validates :user, presence: true
+  validates :followed, presence: true
+  validates :user, uniqueness: { scope: :followed, message: 'This entity is already followed by this user' }
+  
+  after_create :add_subsettings
+  
+  private
+
+  def add_subsettings
+    follow_reason = FollowReason.create!(follow: self)
+    follow_type = FollowType.create!(follow: self)
+  end
+end
diff --git a/app/models/follow_reason.rb b/app/models/follow_reason.rb
new file mode 100644
index 00000000..baf70a6a
--- /dev/null
+++ b/app/models/follow_reason.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+class FollowReason < ApplicationRecord
+  REASONS = %w(created commented contributed followed shared_on starred).freeze
+
+  belongs_to :follow
+  
+  validates :follow, presence: true
+  
+  def has_reason
+    created || commented || contributed || followed || shared_on || starred
+  end
+end
\ No newline at end of file
diff --git a/app/models/follow_type.rb b/app/models/follow_type.rb
new file mode 100644
index 00000000..092070e5
--- /dev/null
+++ b/app/models/follow_type.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+class FollowType < ApplicationRecord
+  belongs_to :follow
+  
+  validates :follow, presence: true
+end
\ No newline at end of file
diff --git a/app/models/map.rb b/app/models/map.rb
index 93137c70..a149a760 100644
--- a/app/models/map.rb
+++ b/app/models/map.rb
@@ -14,6 +14,8 @@ class Map < ApplicationRecord
   has_many :synapses, through: :synapsemappings, source: :mappable, source_type: 'Synapse'
   has_many :messages, as: :resource, dependent: :destroy
   has_many :stars, dependent: :destroy
+  has_many :follows, as: :followed, dependent: :destroy
+  has_many :followers, :through => :follows, source: :user
 
   has_many :access_requests, dependent: :destroy
   has_many :user_maps, dependent: :destroy
@@ -37,6 +39,7 @@ class Map < ApplicationRecord
   # Validate the attached image is image/jpg, image/png, etc
   validates_attachment_content_type :screenshot, content_type: %r{\Aimage/.*\Z}
 
+  after_create :after_created_async
   after_update :after_updated
   after_save :update_deferring_topics_and_synapses, if: :permission_changed?
 
@@ -135,15 +138,25 @@ class Map < ApplicationRecord
     Synapse.where(defer_to_map_id: id).update(permission: permission)
   end
 
-  def invited_text
-    name + ' - invited to edit'
-  end
-
   protected
+  
+  def after_created_async
+    FollowService.follow(self, self.user, 'created')
+    # notify users following the map creator
+  end
+  handle_asynchronously :after_created_async
 
   def after_updated
     return unless ATTRS_TO_WATCH.any? { |k| changed_attributes.key?(k) }
     ActionCable.server.broadcast 'map_' + id.to_s, type: 'mapUpdated'
   end
   
+  def after_updated_async
+    if ATTRS_TO_WATCH.any? { |k| changed_attributes.key?(k) }
+      FollowService.follow(self, updated_by, 'contributed')
+      # NotificationService.notify_followers(self, 'map_updated', changed_attributes)
+      # or better yet publish an event
+    end
+  end
+  handle_asynchronously :after_updated_async
 end
diff --git a/app/models/message.rb b/app/models/message.rb
index d26ce9d6..5e435968 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -6,6 +6,8 @@ class Message < ApplicationRecord
   delegate :name, to: :user, prefix: true
 
   after_create :after_created
+  after_create :after_created_async
+  
 
   def user_image
     user.image.url
@@ -19,4 +21,10 @@ class Message < ApplicationRecord
   def after_created
     ActionCable.server.broadcast 'map_' + resource.id.to_s, type: 'messageCreated', message: as_json
   end
+  
+  def after_created_async
+    FollowService.follow(resource, user, 'commented')
+    NotificationService.notify_followers(resource, 'map_message', self)
+  end
+  handle_asynchronously :after_created_async
 end
diff --git a/app/models/star.rb b/app/models/star.rb
index a49ae2b1..75e3761c 100644
--- a/app/models/star.rb
+++ b/app/models/star.rb
@@ -3,4 +3,19 @@ class Star < ActiveRecord::Base
   belongs_to :user
   belongs_to :map
   validates :map, uniqueness: { scope: :user, message: 'You have already starred this map' }
+
+  after_create :after_created_async
+  before_destroy :before_destroyed
+  
+  protected
+  
+  def after_created_async
+    FollowService.follow(map, user, 'starred')
+    NotificationService.notify_followers(map, 'map_starred', self, 'created')
+  end
+  handle_asynchronously :after_created_async
+  
+  def before_destroyed
+    FollowService.remove_reason(map, user, 'starred')
+  end
 end
diff --git a/app/models/synapse.rb b/app/models/synapse.rb
index c74d8fc4..75cb9759 100644
--- a/app/models/synapse.rb
+++ b/app/models/synapse.rb
@@ -26,7 +26,9 @@ class Synapse < ApplicationRecord
   }
 
   before_create :set_perm_by_defer
+  after_create :after_created_async
   after_update :after_updated
+  before_destroy :before_destroyed
 
   delegate :name, to: :user, prefix: true
 
@@ -72,6 +74,12 @@ class Synapse < ApplicationRecord
   def set_perm_by_defer
     permission = defer_to_map.permission if defer_to_map
   end
+  
+  def after_created_async
+    NotificationService.notify_followers(topic1, 'topic_connected', self)
+    NotificationService.notify_followers(topic2, 'topic_connected', self)
+  end
+  handle_asynchronously :after_created_async
 
   def after_updated
     if ATTRS_TO_WATCH.any? { |k| changed_attributes.key?(k) }
@@ -85,4 +93,10 @@ class Synapse < ApplicationRecord
       end
     end
   end
+  
+  def before_destroyed
+    # hard to know how to do this yet, because the synapse actually gets destroyed
+    #NotificationService.notify_followers(topic1, 'topic_disconnected', self)
+    #NotificationService.notify_followers(topic2, 'topic_disconnected', self)
+  end
 end
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 676ea934..82efb8bc 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -15,12 +15,17 @@ class Topic < ApplicationRecord
 
   has_many :mappings, as: :mappable, dependent: :destroy
   has_many :maps, through: :mappings
+  has_many :follows, as: :followed, dependent: :destroy
+  has_many :followers, :through => :follows, source: :user
 
   belongs_to :metacode
 
   before_create :set_perm_by_defer
   before_create :create_metamap?
+  after_create :after_created_async
   after_update :after_updated
+  after_update :after_updated_async
+  #before_destroy :before_destroyed
 
   validates :permission, presence: true
   validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
@@ -149,6 +154,12 @@ class Topic < ApplicationRecord
     self.link = Rails.application.routes.url_helpers
                      .map_url(host: ENV['MAILER_DEFAULT_URL'], id: @map.id)
   end
+  
+  def after_created_async
+    FollowService.follow(self, self.user, 'created')
+    # notify users following the topic creator
+  end
+  handle_asynchronously :after_created_async
 
   def after_updated
     if ATTRS_TO_WATCH.any? { |k| changed_attributes.key?(k) }
@@ -162,4 +173,16 @@ class Topic < ApplicationRecord
       end
     end
   end
+  
+  def after_updated_async
+    if ATTRS_TO_WATCH.any? { |k| changed_attributes.key?(k) }
+      FollowService.follow(self, updated_by, 'contributed')
+    end
+  end
+  handle_asynchronously :after_updated_async
+
+  def before_destroyed
+    # hard to know how to do this yet, because the topic actually gets destroyed
+    #NotificationService.notify_followers(self, 'topic_deleted', ?)
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 915d06db..e5fadaa9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -12,6 +12,10 @@ class User < ApplicationRecord
   has_many :stars
   has_many :user_maps, dependent: :destroy
   has_many :shared_maps, through: :user_maps, source: :map
+  has_many :follows, as: :followed
+  has_many :followers, through: :follows, source: :user
+
+  has_many :following, class_name: 'Follow'
 
   after_create :generate_code
 
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
new file mode 100644
index 00000000..4fa6dd9d
--- /dev/null
+++ b/app/services/follow_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+class FollowService
+  
+
+  def self.follow(entity, user, reason)
+
+    #return unless is_tester(user)
+
+    follow = Follow.where(followed: entity, user: user).first_or_create
+    if FollowReason::REASONS.include?(reason) && !follow.follow_reason.read_attribute(reason)
+      follow.follow_reason.update_attribute(reason, true)
+    end
+  end
+  
+  def self.unfollow(entity, user)
+    Follow.where(followed: entity, user: user).destroy_all
+  end
+  
+  def self.remove_reason(entity, user, reason)
+    return unless FollowReason::REASONS.include?(reason)
+    follow = Follow.where(followed: entity, user: user).first
+    if follow
+      follow.follow_reason.update_attribute(reason, false)
+      if !follow.follow_reason.has_reason
+        follow.destroy
+      end
+    end
+  end
+  
+  protected
+  
+  def is_tester(user)
+    %w(connorturland@gmail.com devin@callysto.com chessscholar@gmail.com solaureum@gmail.com ishanshapiro@gmail.com).include?(user.email)
+  end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index c590a5ca..9644d23b 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -22,31 +22,124 @@ class NotificationService
         MAILBOXER_CODE_ACCESS_REQUEST
       when 'invite_to_edit'
         MAILBOXER_CODE_INVITE_TO_EDIT
+
+      when 'map_activity'
+        MAILBOXER_CODE_MAP_ACTIVITY
+      when 'map_collaborator_added'
+        MAILBOXER_CODE_MAP_COLLABORATOR_ADDED
+      when 'map_updated'
+        MAILBOXER_CODE_MAP_UPDATED
+      when 'map_message'
+        MAILBOXER_CODE_MAP_MESSAGE
+      when 'map_starred'
+        MAILBOXER_CODE_MAP_STARRED
+        
+      when 'topic_added_to_map'
+        MAILBOXER_CODE_TOPIC_ADDED_TO_MAP
+      when 'topic_connected'
+        MAILBOXER_CODE_TOPIC_CONNECTED
+      when 'topic_deleted'
+        MAILBOXER_CODE_TOPIC_DELETED
+      when 'topic_disconnected'
+        MAILBOXER_CODE_TOPIC_DISCONNECTED
+      when 'topic_updated'
+        MAILBOXER_CODE_TOPIC_UPDATED
     end
   end
 
+  def self.get_subject_for_event(entity, event_type, event)
+    case event_type
+      when 'access_approved' # event is an AccessRequest
+        entity.name + ' - access approved' 
+      when 'access_request' # event is an AccessRequest
+        entity.name + ' - request to edit'
+      when 'invite_to_edit' # event is a UserMap
+        entity.name + ' - invited to edit'
+
+      when 'map_activity' #disabled
+        'placeholder'
+      when 'map_collaborator_added' #disabled
+        'placeholder'
+      when 'map_updated' #disabled
+        'placeholder'
+      when 'map_message' # event is a Message
+        entity.name + ' - received a chat message'
+      when 'map_starred' # event is a Star
+        entity.name + ' was starred by ' + event.user.name
+        
+      when 'topic_added_to_map' # event is an Event::TopicAddedToMap
+        entity.name + ' was added to map ' + event.map.name
+      when 'topic_connected' # event is a Synapse
+        'new synapse to topic ' + entity.name
+      when 'topic_deleted' #disabled
+        'placeholder'
+      when 'topic_disconnected' #disabled
+        'placeholder'
+      when 'topic_updated' #disabled
+        'placeholder'
+    end
+  end
+
+  def self.send_for_follows(follows, entity, event_type, event)
+    return if follows.length == 0
+    template = get_template_for_event_type(event_type)
+    mailboxer_code = get_mailboxer_code_for_event_type(event_type)
+    subject = get_subject_for_event(entity, event_type, event)
+    # we'll prbly want to put the body into the actual loop so we can pass the current user in as a local
+    body = renderer.render(template: template, locals: { entity: entity, event: event }, layout: false)
+    follows.each{|follow|
+      # don't send if they've indicated disinterest in this event_type
+      return unless follow.follow_type.all || follow.follow_type.read_attribute(event_type)
+      # this handles email and in-app notifications, in the future, include push
+      receipt = follow.user.notify(subject, body, event, false, mailboxer_code, (follow.user.emails_allowed && follow.email), event.user)
+      # push could be handled with Actioncable to send transient notifications to the UI
+      # the receipt from the notify call could be used to link to the full notification
+    }
+  end
+
+  def self.notify_followers(entity, event_type, event, reason_filter = nil)
+    follows = entity.follows
+    
+    if reason_filter.class == String && FollowReason::REASONS.include?(reason_filter)
+      follows = follows.joins(:follow_reason).where('follow_reasons.' + reason_filter => true)
+    elsif reason_filter.class == Array
+      # TODO: throw an error here if all the reasons aren't valid
+      follows = follows.joins(:follow_reason).where(reason_filter.map{|r| "follow_reasons.#{r} = 't'"}.join(' OR '))
+    end
+    # filter out the person who took the action
+    follows = follows.to_a.delete_if{|f| f.user.id == event.user.id}
+    send_for_follows(follows, entity, event_type, event)
+  end
+
+
+  # TODO: refactor this
   def self.access_request(request)
     event_type = 'access_request'
     template = get_template_for_event_type(event_type)
     mailboxer_code = get_mailboxer_code_for_event_type(event_type)
+    subject = get_subject_for_event(request.map, event_type, request)
     body = renderer.render(template: template, locals: { map: request.map, request: request }, layout: false)
-    request.map.user.notify(request.requested_text, body, request, false, mailboxer_code, true, request.user)
+    request.map.user.notify(subject, body, request, false, mailboxer_code, true, request.user)
   end
 
+  # TODO: refactor this
   def self.access_approved(request)
     event_type = 'access_approved'
     template = get_template_for_event_type(event_type)
     mailboxer_code = get_mailboxer_code_for_event_type(event_type)
+    subject = get_subject_for_event(request.map, event_type, request)
     body = renderer.render(template: template, locals: { map: request.map }, layout: false)
-    request.user.notify(request.approved_text, body, request, false, mailboxer_code, true, request.map.user)
+    request.user.notify(subject, body, request, false, mailboxer_code, true, request.map.user)
   end
 
+  # TODO: refactor this
   def self.invite_to_edit(user_map)
     event_type = 'invite_to_edit'
     template = get_template_for_event_type(event_type)
     mailboxer_code = get_mailboxer_code_for_event_type(event_type)
+    subject = get_subject_for_event(user_map.map, event_type, user_map)
     body = renderer.render(template: template, locals: { map: user_map.map, inviter: user_map.map.user }, layout: false)
-    user_map.user.notify(user_map.map.invited_text, body, user_map, false, mailboxer_code, true, user_map.map.user)
+    user_map.user.notify(subject, body, user_map, false, mailboxer_code, true, user_map.map.user)
   end
 
   # note: this is a global function, probably called from the rails console with some html body
@@ -71,6 +164,20 @@ class NotificationService
       when MAILBOXER_CODE_INVITE_TO_EDIT
         map = notification.notified_object.map
         'gave you edit access to map  <span class="in-bold">' + map.name + '</span>'
+      when MAILBOXER_CODE_MAP_MESSAGE
+        map = notification.notified_object.resource
+        'commented on map  <span class="in-bold">' + map.name + '</span>'
+      when MAILBOXER_CODE_MAP_STARRED
+        map = notification.notified_object.map
+        'starred map  <span class="in-bold">' + map.name + '</span>'
+      when MAILBOXER_CODE_TOPIC_ADDED_TO_MAP
+        topic = notification.notified_object.eventable
+        map = notification.notified_object.map
+        'added topic <span class="in-bold">' + topic.name + '</span> to map <span class="in-bold">' + map.name + '</span>'  
+      when MAILBOXER_CODE_TOPIC_CONNECTED
+        topic1 = notification.notified_object.topic1
+        topic2 = notification.notified_object.topic2
+        'connected <span class="in-bold">' + topic1.name + '</span> to <span class="in-bold">' + topic2.name + '</span>'
       when MAILBOXER_CODE_MESSAGE_FROM_DEVS
         notification.subject
     end
diff --git a/app/views/map_mailer/map_activity.html.erb b/app/views/map_mailer/map_activity.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/map_activity.text.erb b/app/views/map_mailer/map_activity.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/map_collaborator_added.html.erb b/app/views/map_mailer/map_collaborator_added.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/map_collaborator_added.text.erb b/app/views/map_mailer/map_collaborator_added.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/map_message.html.erb b/app/views/map_mailer/map_message.html.erb
new file mode 100644
index 00000000..621f39f8
--- /dev/null
+++ b/app/views/map_mailer/map_message.html.erb
@@ -0,0 +1,5 @@
+<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
+<p><span style="font-weight: bold;"><%= event.user.name %></span> added a chat message to map <span style="font-weight: bold"><%= entity.name %></span></p>
+<p><%= event.message %></p>
+
+<%= link_to 'Go to Map', map_url(entity), style: button_style %> 
\ No newline at end of file
diff --git a/app/views/map_mailer/map_message.text.erb b/app/views/map_mailer/map_message.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/map_starred.html.erb b/app/views/map_mailer/map_starred.html.erb
new file mode 100644
index 00000000..ac051536
--- /dev/null
+++ b/app/views/map_mailer/map_starred.html.erb
@@ -0,0 +1,3 @@
+<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
+<p><span style="font-weight: bold;"><%= event.user.name %></span> starred map <span style="font-weight: bold"><%= entity.name %></span></p>
+<%= link_to 'Go to Map', map_url(entity), style: button_style %>
\ No newline at end of file
diff --git a/app/views/map_mailer/map_starred.text.erb b/app/views/map_mailer/map_starred.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/map_updated.html.erb b/app/views/map_mailer/map_updated.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/map_updated.text.erb b/app/views/map_mailer/map_updated.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_added_to_map.html.erb b/app/views/map_mailer/topic_added_to_map.html.erb
new file mode 100644
index 00000000..8c61fc64
--- /dev/null
+++ b/app/views/map_mailer/topic_added_to_map.html.erb
@@ -0,0 +1,9 @@
+<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
+<p>
+  <span style="font-weight: bold;"><%= event.user.name %></span>
+  added topic <span style="font-weight: bold"><%= entity.name %></span>
+  to map <span style="font-weight: bold"><%= event.map.name %></span>
+</p>
+
+<%= link_to 'Go to Topic', topic_url(entity), style: button_style %>
+<%= link_to 'Go to Map', map_url(event.map), style: button_style %>
\ No newline at end of file
diff --git a/app/views/map_mailer/topic_added_to_map.text.erb b/app/views/map_mailer/topic_added_to_map.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_connected.html.erb b/app/views/map_mailer/topic_connected.html.erb
new file mode 100644
index 00000000..d190d878
--- /dev/null
+++ b/app/views/map_mailer/topic_connected.html.erb
@@ -0,0 +1,15 @@
+<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
+
+<% topic1 = entity %>
+<% topic2 = (entity.id == event.topic1_id ? event.topic2 : event.topic1) %>
+
+<p>
+  <span style="font-weight: bold;"><%= event.user.name %></span>
+  connected topic <span style="font-weight: bold"><%= topic1.name %></span>
+  to topic <span style="font-weight: bold"><%= topic2.name %></span>
+  <% if event.desc %>
+    with the description "<%= event.desc %>".
+  <% end %>
+</p>
+
+<%= link_to 'View the connection', topic_url(topic1), style: button_style %>
\ No newline at end of file
diff --git a/app/views/map_mailer/topic_connected.text.erb b/app/views/map_mailer/topic_connected.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_deleted.html.erb b/app/views/map_mailer/topic_deleted.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_deleted.text.erb b/app/views/map_mailer/topic_deleted.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_disconnected.html.erb b/app/views/map_mailer/topic_disconnected.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_disconnected.text.erb b/app/views/map_mailer/topic_disconnected.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_updated.html.erb b/app/views/map_mailer/topic_updated.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/map_mailer/topic_updated.text.erb b/app/views/map_mailer/topic_updated.text.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/config/initializers/mailboxer.rb b/config/initializers/mailboxer.rb
index cc0b520c..8409132a 100644
--- a/config/initializers/mailboxer.rb
+++ b/config/initializers/mailboxer.rb
@@ -7,11 +7,31 @@
 #   notification_code: MAILBOXER_CODE_ACCESS_REQUEST
 # },
 # which would imply that this is an access request to Map.find(1)
-MAILBOXER_CODE_ACCESS_REQUEST = 'ACCESS_REQUEST'
-MAILBOXER_CODE_ACCESS_APPROVED = 'ACCESS_APPROVED'
-MAILBOXER_CODE_INVITE_TO_EDIT = 'INVITE_TO_EDIT'
 MAILBOXER_CODE_MESSAGE_FROM_DEVS = 'MESSAGE_FROM_DEVS'
 
+# these ones are old and can't change without a migration
+MAILBOXER_CODE_ACCESS_APPROVED          = 'ACCESS_APPROVED'
+MAILBOXER_CODE_ACCESS_REQUEST           = 'ACCESS_REQUEST'
+MAILBOXER_CODE_INVITE_TO_EDIT           = 'INVITE_TO_EDIT'
+
+# these ones are new
+# this one's a catch all for edits ON the map
+MAILBOXER_CODE_MAP_ACTIVITY             = 'MAP_ACTIVITY'
+# MAILBOXER_CODE_MAP_ADDED_TO_MAP       = 'MAP_ADDED_TO_MAP'
+MAILBOXER_CODE_MAP_COLLABORATOR_ADDED   = 'MAP_COLLABORATOR_ADDED'
+# BOTHER WITH MAP_COLLABORATOR_REMOVED ?
+MAILBOXER_CODE_MAP_UPDATED               = 'MAP_UPDATED' # this refers to the map object itself: name, desc, permission, etc
+# ADD MAP_FORKED
+MAILBOXER_CODE_MAP_MESSAGE              = 'MAP_MESSAGE'
+MAILBOXER_CODE_MAP_STARRED              = 'MAP_STARRED'
+
+MAILBOXER_CODE_TOPIC_ADDED_TO_MAP       = 'TOPIC_ADDED_TO_MAP'
+MAILBOXER_CODE_TOPIC_CONNECTED          = 'TOPIC_CONNECTED'
+MAILBOXER_CODE_TOPIC_DELETED            = 'TOPIC_DELETED'
+MAILBOXER_CODE_TOPIC_DISCONNECTED       = 'TOPIC_DISCONNECTED'
+MAILBOXER_CODE_TOPIC_UPDATED            = 'TOPIC_UPDATED'
+# BOTHER WITH TOPIC_REMOVED_FROM_MAP ?
+
 Mailboxer.setup do |config|
   # Configures if your application uses or not email sending for Notifications and Messages
   config.uses_emails = true
diff --git a/db/migrate/20170209215819_create_follows.rb b/db/migrate/20170209215819_create_follows.rb
new file mode 100644
index 00000000..2999a643
--- /dev/null
+++ b/db/migrate/20170209215819_create_follows.rb
@@ -0,0 +1,12 @@
+class CreateFollows < ActiveRecord::Migration[5.0]
+  def change
+    create_table :follows do |t|
+      t.references  :user, index: true
+      t.boolean     :email, default: true
+      t.boolean     :app, default: true
+      t.boolean     :push, default: true
+      t.references  :followed, polymorphic: true, index: true
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20170209215911_create_follow_reasons.rb b/db/migrate/20170209215911_create_follow_reasons.rb
new file mode 100644
index 00000000..3f29af06
--- /dev/null
+++ b/db/migrate/20170209215911_create_follow_reasons.rb
@@ -0,0 +1,14 @@
+class CreateFollowReasons < ActiveRecord::Migration[5.0]
+  def change
+    create_table :follow_reasons do |t|
+      t.references  :follow, index: true
+      t.boolean     :created
+      t.boolean     :contributed
+      t.boolean     :commented
+      t.boolean     :followed
+      t.boolean     :shared_on
+      t.boolean     :starred
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20170209215920_create_follow_types.rb b/db/migrate/20170209215920_create_follow_types.rb
new file mode 100644
index 00000000..9bf1353a
--- /dev/null
+++ b/db/migrate/20170209215920_create_follow_types.rb
@@ -0,0 +1,22 @@
+class CreateFollowTypes < ActiveRecord::Migration[5.0]
+  def change
+    create_table :follow_types do |t|
+      t.references  :follow, index: true
+      t.boolean     :all, default: true
+      t.boolean     :acccess_approved
+      t.boolean     :access_request
+      t.boolean     :invite_to_edit
+      t.boolean     :map_activity
+      t.boolean     :map_collaborator_added
+      t.boolean     :map_message
+      t.boolean     :map_starred
+      t.boolean     :map_updated
+      t.boolean     :topic_added_to_map
+      t.boolean     :topic_connected
+      t.boolean     :topic_deleted
+      t.boolean     :topic_disconnected
+      t.boolean     :topic_updated
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a2fc31b1..235ab552 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20170208161305) do
+ActiveRecord::Schema.define(version: 20170209215920) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -67,6 +67,53 @@ ActiveRecord::Schema.define(version: 20170208161305) do
     t.index ["user_id"], name: "index_events_on_user_id", using: :btree
   end
 
+  create_table "follow_reasons", force: :cascade do |t|
+    t.integer  "follow_id"
+    t.boolean  "created"
+    t.boolean  "contributed"
+    t.boolean  "commented"
+    t.boolean  "followed"
+    t.boolean  "shared_on"
+    t.boolean  "starred"
+    t.datetime "created_at",  null: false
+    t.datetime "updated_at",  null: false
+    t.index ["follow_id"], name: "index_follow_reasons_on_follow_id", using: :btree
+  end
+
+  create_table "follow_types", force: :cascade do |t|
+    t.integer  "follow_id"
+    t.boolean  "all",                    default: true
+    t.boolean  "acccess_approved"
+    t.boolean  "access_request"
+    t.boolean  "invite_to_edit"
+    t.boolean  "map_activity"
+    t.boolean  "map_collaborator_added"
+    t.boolean  "map_message"
+    t.boolean  "map_starred"
+    t.boolean  "map_updated"
+    t.boolean  "topic_added_to_map"
+    t.boolean  "topic_connected"
+    t.boolean  "topic_deleted"
+    t.boolean  "topic_disconnected"
+    t.boolean  "topic_updated"
+    t.datetime "created_at",                            null: false
+    t.datetime "updated_at",                            null: false
+    t.index ["follow_id"], name: "index_follow_types_on_follow_id", using: :btree
+  end
+
+  create_table "follows", force: :cascade do |t|
+    t.integer  "user_id"
+    t.boolean  "email",         default: true
+    t.boolean  "app",           default: true
+    t.boolean  "push",          default: true
+    t.string   "followed_type"
+    t.integer  "followed_id"
+    t.datetime "created_at",                   null: false
+    t.datetime "updated_at",                   null: false
+    t.index ["followed_type", "followed_id"], name: "index_follows_on_followed_type_and_followed_id", using: :btree
+    t.index ["user_id"], name: "index_follows_on_user_id", using: :btree
+  end
+
   create_table "in_metacode_sets", force: :cascade do |t|
     t.integer  "metacode_id"
     t.integer  "metacode_set_id"