rough in feedback email
diff --git a/www/board/agenda/Gemfile b/www/board/agenda/Gemfile
index 335df4b..85c171f 100644
--- a/www/board/agenda/Gemfile
+++ b/www/board/agenda/Gemfile
@@ -29,6 +29,6 @@
   gem 'poltergeist'
 end
 
-group :demo do
-  gem 'puma'
+group :development do
+  gem 'passenger'
 end
diff --git a/www/board/agenda/Rakefile b/www/board/agenda/Rakefile
index 70ca673..f07de50 100644
--- a/www/board/agenda/Rakefile
+++ b/www/board/agenda/Rakefile
@@ -6,9 +6,10 @@
 task :spec => 'test:setup'
 task :test => :spec
 
-task :server do
+task :server => [:bundle, :listen] do
   ENV['RACK_ENV']='development'
-  require 'wunderbar/listen'
+  at_exit {sleep 0.5}
+  sh 'bundle exec passenger start'
 end
 
 namespace :server do
@@ -145,3 +146,27 @@
     end
   end
 end
+
+task :bundle => 'Gemfile.lock' do
+  require 'bundler'
+  Bundler.require(:default, :development)
+end
+
+# restart server when files update
+task :listen => :bundle do
+  dirs = [
+    File.expand_path('..', File.realpath(__FILE__)),
+    File.expand_path('../../../../lib', File.realpath(__FILE__))
+  ]
+
+  listener = Listen.to(*dirs) do |modified, added, removed|
+    puts "detected update: #{(modified + added + removed).join(', ')}"
+    FileUtils.touch "#{dirs.first}/tmp/restart.txt"
+  end
+
+  listener.ignore /~$/
+  listener.ignore /^\..*\.sw\w$/
+  listener.ignore /passenger.\d+\.(log|pid(\.lock)?)$/
+
+  listener.start
+end
diff --git a/www/board/agenda/routes.rb b/www/board/agenda/routes.rb
index 9489ba7..8130cc0 100755
--- a/www/board/agenda/routes.rb
+++ b/www/board/agenda/routes.rb
@@ -2,6 +2,15 @@
 # Server side Sinatra routes
 #
 
+# temporary
+get %r{^/(\d\d\d\d-\d\d-\d\d)/feedback$} do |date|
+  _html :feedback
+end
+get %r{^/(\d\d\d\d-\d\d-\d\d)/feedback.json$} do |date|
+  @agenda = "board_agenda_#{date.gsub('-', '_')}.txt".untaint
+  _json :'actions/feedback'
+end
+
 # redirect root to latest agenda
 get '/' do
   agenda = dir('board_agenda_*.txt').sort.last
@@ -225,4 +234,3 @@
     end
   end
 end
-
diff --git a/www/board/agenda/views/actions/feedback.json.rb b/www/board/agenda/views/actions/feedback.json.rb
new file mode 100644
index 0000000..b4de899
--- /dev/null
+++ b/www/board/agenda/views/actions/feedback.json.rb
@@ -0,0 +1,72 @@
+#
+# send feedback on reports
+#
+
+# fetch minutes
+@minutes = @agenda.sub('_agenda_', '_minutes_')
+minutes_file = "#{AGENDA_WORK}/#{@minutes.sub('.txt', '.yml')}"
+minutes_file.untaint if @minutes =~ /^board_minutes_\d+_\d+_\d+\.txt$/
+date = @agenda[/\d+_\d+_\d+/].gsub('_', '-')
+
+if File.exist? minutes_file
+  minutes = YAML.load_file(minutes_file) || {}
+else
+  minutes = {}
+end
+
+# utilize smtp without certificate verification
+Mail.defaults do
+  delivery_method :smtp, openssl_verify_mode: 'none'
+end
+
+# extract values for common fields
+unless @from
+  sender = ASF::Person.find(env.user || ENV['USER'])
+  @from = "#{sender.public_name} <#{sender.id}@apache.org>".untaint
+end
+
+output = []
+
+# iterate over the agenda
+Agenda.parse(@agenda, :full).each do |item|
+  # select exec officer, additional officer, and committee reports
+  next unless item[:attach] =~ /^(4[A-Z]|\d|[A-Z]+)$/
+  next unless item['chair_email']
+
+  text = ''
+
+  if item['comments'] and not item['comments'].empty?
+    text += "\nComments:\n#{item['comments'].gsub(/^/, '  ')}\n"
+  end
+
+  if minutes[item['title']]
+    text += "\nMinutes:\n#{minutes[item['title']].gsub(/^/, '  ')}\n"
+  end
+
+  next if text.strip.empty?
+
+  # construct email
+  mail = Mail.new do
+    from @from
+    to "#{item['owner']} <#{item['chair_email']}>".untaint
+    subject "Board feedback on #{date} #{item['title']} report"
+
+    if item['mail_list']
+      if item[:attach] =~ /^[A-Z]+/
+        cc "private@#{item['mail_list']}.apache.org".untaint
+      else
+        cc "#{item['mail_list']}@apache.org".untaint
+      end
+    end
+
+    body text.strip.untaint
+  end
+
+  output << {
+    attach: item[:attach],
+    title: item['title'],
+    mail: mail.to_s
+  }
+end
+
+output
diff --git a/www/board/agenda/views/feedback.html.rb b/www/board/agenda/views/feedback.html.rb
new file mode 100644
index 0000000..6aa590c
--- /dev/null
+++ b/www/board/agenda/views/feedback.html.rb
@@ -0,0 +1,20 @@
+_html do
+  _body do
+    _p 'loading'
+
+    _script %{
+      jQuery.getJSON('feedback.json', function(data) {
+        data.forEach(function(message) {
+          var h1 = document.createElement('h1');
+          h1.setAttribute('id', message.title);
+          h1.textContent = message.title;
+          var pre = document.createElement('pre');
+          pre.textContent = message.mail;
+          document.body.appendChild(h1);
+          document.body.appendChild(pre);
+        });
+        document.querySelector('p').remove();
+      });
+    }
+  end
+end