<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>knwang (Kevin Wang)</title>
    <link>https://ruby-china.org/knwang</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>搭建一个点对点的分布式文件共享系统 - Launch School 毕业项目</title>
      <description>&lt;p&gt;分享一个 &lt;a href="https://launchschool.com/" rel="nofollow" target="_blank" title=""&gt;Launch School&lt;/a&gt; 最新的毕业项目 - 一个用 Ruby 实现的类似 BitTorrent 的分布式文件共享和传输系统。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xorro-p2p.github.io/" rel="nofollow" target="_blank" title=""&gt;英文介绍&lt;/a&gt;  &lt;a href="https://xorro-p2p.github.io/zh-tw/" rel="nofollow" target="_blank" title=""&gt;中文介绍&lt;/a&gt; &lt;a href="https://github.com/xorro-p2p/xorro" rel="nofollow" target="_blank" title=""&gt;Source Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;项目介绍里面有如何构建点对点分布式系统的思路介绍和工程考虑。欢迎有对分布式系统感兴趣的同学关注该项目。 &lt;/p&gt;

&lt;p&gt;这个项目由三个学生完成，一周调研和设计，三周编程实现。项目主要集中在分布式系统的设计和测试，和针对 Kedemlia 的分布式 routing 系统的实现。由于时间关系，针对文件的工具以及传输层面并没有来的及优化。&lt;/p&gt;

&lt;p&gt;最后。。推荐一下该项目的主力 &lt;a href="https://newfishg.github.io/" rel="nofollow" target="_blank" title=""&gt;陈欣俞同学&lt;/a&gt; 正在找工作。能参加到 Launch School 的 毕业项目的都已经经过了学校内部的多伦测试和考验。我们对毕业生有信心，也希望他们能去很好的公司就职。陈欣俞目前坐标台北，也可以考虑合适的远程工作。&lt;/p&gt;

&lt;p&gt;顺面分享下以往的学生毕业项目：&lt;/p&gt;

&lt;hr&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://workerholic.github.io/" rel="nofollow" target="_blank" title=""&gt;Workerholic&lt;/a&gt; - An Efficient and Scalable Background Processor from Scratch (Ruby)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tracebin/tracebin-ruby" rel="nofollow" target="_blank" title=""&gt;TraceBin&lt;/a&gt; - Ruby Based Performance Monitoring Agent and Service (Ruby)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/allograph/allograph" rel="nofollow" target="_blank" title=""&gt;AlloGraph&lt;/a&gt; - A Framework to Build GraphQL servers from Database or RESTful APIs (JavaScript/Node.js)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/acorncache/acorn-cache" rel="nofollow" target="_blank" title=""&gt;Acorn Cache&lt;/a&gt; - A Configurable HTTP Proxy Caching Library (Ruby)&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>knwang</author>
      <pubDate>Sat, 09 Sep 2017 00:03:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/34080</link>
      <guid>https://ruby-china.org/topics/34080</guid>
    </item>
    <item>
      <title>Tracebin - 开源的 Ruby Application 性能监控应用 (APM)</title>
      <description>&lt;p&gt;是我们一个学生的毕业项目，用了大概一个月实现的。请大家拍砖：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://traceb.in/" rel="nofollow" target="_blank"&gt;https://traceb.in/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;代码在这里：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tracebin/tracebin-ruby" rel="nofollow" target="_blank"&gt;https://github.com/tracebin/tracebin-ruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一些实现的细节／心得： &lt;a href="https://traceb.in/story" rel="nofollow" target="_blank"&gt;https://traceb.in/story&lt;/a&gt;&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Sat, 03 Jun 2017 03:59:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/33126</link>
      <guid>https://ruby-china.org/topics/33126</guid>
    </item>
    <item>
      <title>AcornCache - 基于 Rack 的 HTTP 缓存中间件</title>
      <description>&lt;p&gt;看这里：&lt;a href="https://github.com/acorncache/acorn-cache" rel="nofollow" target="_blank"&gt;https://github.com/acorncache/acorn-cache&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个是最近带着两个我们的学生做的他们的毕业项目。&lt;/p&gt;

&lt;p&gt;主要亮点：&lt;/p&gt;

&lt;p&gt;－ Rack 中间件，即插即用
－ 兼容 HTTP 标准（RFC 2616)
－ 支持 Redis 和 Memcached 作为缓存存储&lt;/p&gt;

&lt;p&gt;相对于已有的 Rack::Cache, 提供了服务器端可配置的 Page Rules, 可以针对不同的路径配置缓存方式以及 定制：TTL 时间，忽略 query params ( marketing 加的 utm tag 什么的可以直接忽略), 和比较保守的 must_revalidate 头设置（检查 etags 和 last_modified)&lt;/p&gt;

&lt;p&gt;因为这些设置，acorn_cache 可以用于对一致性要求不大，但流量很大的动态页面，比如包括一些动态内容的网站首页，或者数据库数据驱动的’准“静态页面。这些功能可以用 Varnish 或者在一些高端的 CDN 比如 cloudflare 里面来实现，但是 acorn_cache 因为是纯 Ruby 实现可以支持到任意 Regex URL 匹配，用起来会比较方便。在类似 Heroku 这类 PaaS 环境也会很有用。 （他们的二期计划是做一个 Heroku 的 Addon 服务来托管）&lt;/p&gt;

&lt;p&gt;请大家在功能和代码层面都提意见 － 他们现在在写系统层面的测试，应该几天后这方面会完善。 &lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Thu, 03 Mar 2016 03:12:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/29183</link>
      <guid>https://ruby-china.org/topics/29183</guid>
    </item>
    <item>
      <title>Ruby 练习题：打战舰</title>
      <description>&lt;p&gt;&lt;a href="https://launchschool.com" rel="nofollow" target="_blank" title=""&gt;我们&lt;/a&gt; 的假期 Ruby 练习题：&lt;/p&gt;

&lt;p&gt;论坛好像不能直接上 video,  就把连接贴过来，需要下载下视频 (英文，但应该跟着看比较好理解)：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://d1b1wr57ag5rdp.cloudfront.net/videos/output/battleship_game_f38fa9_s349/battleship_game_f38fa9_s349.mp4" rel="nofollow" target="_blank"&gt;https://d1b1wr57ag5rdp.cloudfront.net/videos/output/battleship_game_f38fa9_s349/battleship_game_f38fa9_s349.mp4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;欢迎大家尝试。我过两天会给提示，在新年后会给详细解答。&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Sun, 27 Dec 2015 14:21:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/28544</link>
      <guid>https://ruby-china.org/topics/28544</guid>
    </item>
    <item>
      <title>Introduction to Programming with Ruby - 给 Ruby 新手写的免费电子书</title>
      <description>&lt;p&gt;我们最近写了一本给 Ruby 新手的电子书，在这里：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gotealeaf.com/books/ruby" rel="nofollow" target="_blank" title=""&gt;https://launchschool.com/books/ruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;写这本书的原因有几个：&lt;/p&gt;

&lt;p&gt;1) 市面上很多写给有经验程序员转学 Ruby 的书籍，很少有针对完全没有或者很少程序基础的新手
2) 学习 Rails 需要对 Ruby 有所掌握，但在初始阶段不必了解太深，而是要把基础部分多练习和彻底掌握
3) 很多其他的入门书或者教程学习门槛太高，太陡峭，而且包括很多非 Ruby 的知识，比如很多很快进入 Rails，gems, 甚至 metaprogramming 这些。&lt;/p&gt;

&lt;p&gt;这本书的特点是从最基础讲起，只涵盖简单的部分，而且伴随有大量的练习以及对于练习的视频讲解。这本书学习下来应该只要几个小时，但对 Ruby 基础和编程基本的思路会有很好的掌握。 &lt;/p&gt;

&lt;p&gt;第一次写书而且是第一版，希望大家多提意见反馈，和纠错。 &lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Fri, 13 Jun 2014 08:35:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/19914</link>
      <guid>https://ruby-china.org/topics/19914</guid>
    </item>
    <item>
      <title>为什么你应该永不用 MongoDB (转)</title>
      <description>&lt;p&gt;原文在 Sarah Mei 的个人博客上&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/" rel="nofollow" target="_blank"&gt;http://www.sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我以前工作过的一个项目很类似，开始看的时候很适合 Document types, 越到后来业务需求越复杂越是把 Mongo  硬当作关系数据库用&lt;/p&gt;

&lt;p&gt;个人感觉越是贴近业务的数据越要考虑 关系型，越是非业务型的数据可以考虑 其他的方案&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Fri, 22 Nov 2013 06:22:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/15720</link>
      <guid>https://ruby-china.org/topics/15720</guid>
    </item>
    <item>
      <title>程序员，创业者，和抑郁症</title>
      <description>&lt;p&gt;上周参加了 Business of Software 2013，在整个大会上反响最大的就是这个 talk, 演讲结束后得到了全场起立鼓掌。会议的组织者当场决定在最快的速度把这个演讲发布出来，来能帮助更多的人。感觉在国内对抑郁症和心理疾病更不多有支持群体和关怀，希望这个会对一些朋友有帮助。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://businessofsoftware.org/speaker/greg-baugues-add/" rel="nofollow" target="_blank"&gt;http://businessofsoftware.org/speaker/greg-baugues-add/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;几个补充&lt;/p&gt;

&lt;p&gt;1）当 Greg 问到现场的人多少人身边有受抑郁症困扰的朋友，一大半人举了手
2）当 Greg 问到现场的人多少人自己身受抑郁症的困扰，20％ － 30% 的人举了手
3）Greg 演讲中提到的 Caleb, 原来是我在 Hashrocket 的同事，他的突然过世让我们所有人震惊和惋惜。我们有感觉他可能需要帮助，但是都没有给他足够的关怀。希望这样的悲剧不再重演。&lt;/p&gt;

&lt;p&gt;请分享给你周围有可能需要帮助的朋友。&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Tue, 05 Nov 2013 05:30:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/15302</link>
      <guid>https://ruby-china.org/topics/15302</guid>
    </item>
    <item>
      <title>下周北京上海的活动</title>
      <description>&lt;p&gt;下周北京或者上海有 Ruby / 技术 / 创业圈的活动么？帮一个朋友问。 &lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Sat, 13 Jul 2013 16:22:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/12483</link>
      <guid>https://ruby-china.org/topics/12483</guid>
    </item>
    <item>
      <title>GoGaRuCo</title>
      <description>&lt;p&gt;&lt;a href="http://gogaruco.com/" rel="nofollow" target="_blank" title=""&gt;Golden Gate Ruby Conference&lt;/a&gt; (在 San Francisco) 开始售票了，有想去的朋友么？ &lt;/p&gt;

&lt;p&gt;说起来也是距离中国最近的 Ruby Conf 之一喔。今年 speaker 阵容很强大，而且目测 Ember 会不少发声&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Thu, 04 Jul 2013 03:27:23 +0800</pubDate>
      <link>https://ruby-china.org/topics/12230</link>
      <guid>https://ruby-china.org/topics/12230</guid>
    </item>
    <item>
      <title>数字游牧者</title>
      <description>&lt;p&gt;西贡看起来真的不错，如果年轻十岁可以考虑去那儿住一两年，做做项目，认识认识人&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/design-startups/fc9744367386" rel="nofollow" target="_blank"&gt;https://medium.com/design-startups/fc9744367386&lt;/a&gt;&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Fri, 28 Jun 2013 06:24:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/12062</link>
      <guid>https://ruby-china.org/topics/12062</guid>
    </item>
    <item>
      <title>Ninja Block</title>
      <description>&lt;p&gt;有人玩这个么？&lt;/p&gt;

&lt;p&gt;&lt;a href="http://ninjablocks.com/" rel="nofollow" target="_blank"&gt;http://ninjablocks.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;看起来很有意思，而且开发起来会很简单&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Tue, 11 Jun 2013 11:45:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/11642</link>
      <guid>https://ruby-china.org/topics/11642</guid>
    </item>
    <item>
      <title>找美国工作的同学们</title>
      <description>&lt;p&gt;这个不适合所有人，但对于有些经济能力，对自己的能力也比较自信的，可以就申请旅游签证去三藩，interview 个十几个公司，如果确实有技术，拿 offer 然后回国等工作签证都是可能的，我也见过这样的例子。三藩／硅谷对于工程师的需求确实已经到了难以置信的地步。&lt;/p&gt;

&lt;p&gt;不成就是去三藩玩一圈散散心。 &lt;/p&gt;

&lt;p&gt;&lt;a href="http://blog.penso.info/2013/05/25/getting-a-job-in-san-francisco/" rel="nofollow" target="_blank"&gt;http://blog.penso.info/2013/05/25/getting-a-job-in-san-francisco/&lt;/a&gt;&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Sun, 26 May 2013 00:26:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/11234</link>
      <guid>https://ruby-china.org/topics/11234</guid>
    </item>
    <item>
      <title>Teahour.fm 第十八期问题征集</title>
      <description>&lt;p&gt;第十八期的 Teahour.fm podcast 我们已经请到了 &lt;a href="http://about.me/coderoshi" rel="nofollow" target="_blank" title=""&gt;Eric Redmond&lt;/a&gt;, &lt;a href="http://www.amazon.com/Seven-Databases-Weeks-Modern-Movement/dp/1934356921" rel="nofollow" target="_blank" title=""&gt;Seven Databases in Seven Weeks&lt;/a&gt; 和 &lt;a href="https://github.com/coderoshi/little_riak_book" rel="nofollow" target="_blank" title=""&gt;Little Riak Book&lt;/a&gt; 的作者，数据库和分布式系统的专家。 &lt;/p&gt;

&lt;p&gt;这期的主题会主要对各种数据库技术进行讨论，比较和选择时候的考虑因素。在这期节目里我们想做一个“闪电回答”的环节，大家在这里提问题，我们挑选一些让 Eric 回答。请最好问些大方向的问题，而不是类如某个数据库的某个参数怎么用，更适合 podcast 的形式。 &lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Wed, 22 May 2013 10:30:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/11145</link>
      <guid>https://ruby-china.org/topics/11145</guid>
    </item>
    <item>
      <title>创业者是新式劳工</title>
      <description>&lt;p&gt;&lt;a href="http://www.forbes.com/sites/venkateshrao/2012/09/03/entrepreneurs-are-the-new-labor-part-i/" rel="nofollow" target="_blank"&gt;http://www.forbes.com/sites/venkateshrao/2012/09/03/entrepreneurs-are-the-new-labor-part-i/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;今年看到的最好文章之一。国内的情况我不太了解，但文章里的观点趋势在美国可以切身体会到。 &lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Tue, 07 May 2013 01:32:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/10772</link>
      <guid>https://ruby-china.org/topics/10772</guid>
    </item>
    <item>
      <title>被学生恶搞了</title>
      <description>&lt;p&gt;&lt;span class="embed-responsive embed-responsive-16by9"&gt;&lt;iframe class="embed-responsive-item" src="//www.youtube.com/embed/6r0hW7isvNw" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;眼泪笑出来了&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Thu, 28 Mar 2013 03:59:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/9798</link>
      <guid>https://ruby-china.org/topics/9798</guid>
    </item>
    <item>
      <title>Teahour.fm 英文内容</title>
      <description>&lt;p&gt;我想听听社区对 Teahour Podcast 不定期邀请用英文的嘉宾做采访是怎样的想法。这样可以让我们非常大的增多可以触及的题目和嘉宾，和给大家带来不同的视角。&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Sun, 10 Mar 2013 05:26:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/9291</link>
      <guid>https://ruby-china.org/topics/9291</guid>
    </item>
    <item>
      <title>除夕夜你还看春晚？</title>
      <description>&lt;p&gt;Teahour Podcast 精心推出除夕特辑挑战春晚，特邀神秘嘉宾谈个人成长和团队建设。80 分钟超强度节目，将在除夕夜推出！&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Fri, 08 Feb 2013 11:48:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/8648</link>
      <guid>https://ruby-china.org/topics/8648</guid>
    </item>
    <item>
      <title>Open Source Rails 重新上线了</title>
      <description>&lt;p&gt;&lt;a href="http://www.opensourcerails.com/" rel="nofollow" target="_blank" title=""&gt;Open Source Rails&lt;/a&gt; 重新上线了！上线后 app 的&lt;a href="http://www.opensourcerails.com/relaunch/" rel="nofollow" target="_blank" title=""&gt;重点&lt;/a&gt;是： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;只挑选介绍最好的开源项目&lt;/li&gt;
&lt;li&gt;方便发现新的项目&lt;/li&gt;
&lt;li&gt;深度介绍讲解&lt;/li&gt;
&lt;li&gt; 社区驱动&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注：&lt;a href="http://www.gotealeaf.com" rel="nofollow" target="_blank" title=""&gt;Tealeaf Academy&lt;/a&gt; 是在 Open Source Rails 后面的团队之一。如果你有任何建议，或者想贡献内容，可以直接到这里： &lt;a href="https://github.com/railsjedi/opensourcerails/issues/new?title=Submit%20a%20new%20project&amp;amp;body=Description%20of%20Project&amp;amp;labels=New%20Project" rel="nofollow" target="_blank"&gt;https://github.com/railsjedi/opensourcerails/issues/new?title=Submit%20a%20new%20project&amp;amp;body=Description%20of%20Project&amp;amp;labels=New%20Project&lt;/a&gt;&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Wed, 23 Jan 2013 02:34:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/8282</link>
      <guid>https://ruby-china.org/topics/8282</guid>
    </item>
    <item>
      <title>用 Mailgun 来做的讨论版 Email 辅助流程 (很多代码)</title>
      <description>&lt;p&gt;我给 Mailgun 写了一篇博客，原文在这里 - &lt;a href="http://blog.mailgun.net/post/40719408774/ruby-tutorial-how-tealeaf-academy-increased-student" rel="nofollow" target="_blank"&gt;http://blog.mailgun.net/post/40719408774/ruby-tutorial-how-tealeaf-academy-increased-student&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;At &lt;a href="http://www.gotealeaf.com" rel="nofollow" target="_blank" title=""&gt;Tealeaf Academy&lt;/a&gt;, creating a “Study Together, Progress Together” experience for our students is at core of our way of teaching.  One of our core tools is the discussion board where students ask questions, share ideas, collaborate on homework assignments, and teachers quickly jump in to help students get unstuck on problems. One of our recent priorities was to reduce friction in discussion board usage and encourage more discussions with a complementary email notification and a “reply-to email to post on discussion board” workflow.  Once we implemented the below code using the Mailgun Routes API, activity on our discussion board increased three fold, and questions are now typically getting answered within an hour, sometimes even minutes, and students are able to move on the next set of tasks a lot quicker. Here’s how we did it:&lt;/p&gt;

&lt;p&gt;Our workflow would go as the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a post (most likely, a question) is created, all course participants are notified by email notification&lt;/li&gt;
&lt;li&gt;Course participants can reply to the email notifications directly from their email inbox, without having to sign into the course&lt;/li&gt;
&lt;li&gt;That reply will be posted on the online discussion board, and are also sent to other course participants, to keep the conversation going.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/af97e6569a5cb997a3f5513716ccfbfe.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="Why we choose Mailgun"&gt;Why we choose Mailgun&lt;/h3&gt;
&lt;p&gt;The key piece of this workflow is to receive and parse inbound email messages. We looked around for several email service providers, and in the end picked Mailgun because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is very developer friendly - We are developers, and Mailgun speaks to us. The APIs expose a lot of low level options that allows tweaking. We like the Routes in particular -It’s a nice layer of abstraction that makes integration with apps very easy. (see how we use it below)&lt;/li&gt;
&lt;li&gt;It is the most feature complete service we have found - we can use Mailgun for transactional emails, campaigns as well as email lists - it’s nice to have just one service provider to handle everything we need.&lt;/li&gt;
&lt;li&gt;The price is reasonable and the upgrading path to dedicated IP and custom DKIM is nice, even we do not need it yet.&lt;/li&gt;
&lt;li&gt;The support is top notch. There is a live chat that I can talk to their developers directly on issues and it has been very useful for us to get issues resolved.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Setting up the email infrastructure"&gt;Setting up the email infrastructure&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The first step was to create a new domain on Mailgun. In our case, it’s messaging.gotealeaf.com which we use for sending and receiving emails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, we created a MailgunGateway as our wrapper for Mailgun’s API. This wrapper takes care of all our interactions with Mailgun through our account. Here we use Mailgun’s send_batch_messages API to send HTML emails. It allows us to send a message to multiple receivers with a single API call. We keep our API key as environment variables on the server for extra security. There is also a simple delivery filter such that no emails are sent in the development environment; On staging, all emails are sent to Chris (my co-founder) and myself, so we can test out things without fearing to spam our users; Only on the production environment emails are sent to real users.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MailgunGateway&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_batch_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="no"&gt;RestClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messaging_api_end_point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;from: &lt;/span&gt;&lt;span class="n"&gt;default_sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;delivery_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:to&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:subject&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;:"h:Reply-To"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:reply_to&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;:"recipient-variables"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:recipient_variables&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;staging?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_sender&lt;/span&gt;
    &lt;span class="s2"&gt;"Tealeaf Academy "&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;api_key&lt;/span&gt;
    &lt;span class="vi"&gt;@api_key&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'mailgun_api_key'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;messaging_api_end_point&lt;/span&gt;
    &lt;span class="vi"&gt;@messaging_api_end_piont&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s2"&gt;"https://api:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@api.mailgun.net/v2/messaging.gotealeaf.com/messages"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delivery_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;emails&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"kevin@gotealeaf.com, chris@gotealeaf.com"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Add tracking token for posts to identify the “thread” that comments should be collated to"&gt;Add tracking token for posts to identify the “thread” that comments should be collated to&lt;/h3&gt;
&lt;p&gt;Every post carries a tracking token, which will be included in the “Reply-To” header in the email to collate inbound email replies to the corresponding thread.  This method makes it very easy to post the correct reply to the correct thread.&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Tokenable&lt;/span&gt;

  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:course&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order: &lt;/span&gt;&lt;span class="s2"&gt;"created_at ASC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Tokenable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;//&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;commenter_name&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Send email notifications for new post or comment"&gt;Send email notifications for new post or comment&lt;/h3&gt;
&lt;p&gt;Once a post or comment is created, we send an email notification to all course participants. We are sending emails synchronously for now, but as we have more users, we’ll probably want to offload this to a background job.&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Courses::PostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;AuthenticatedController&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="no"&gt;CourseNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notify_course_participants_on_new_discussion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;course_home_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Courses::Posts::CommentsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;AuthenticatedController&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="no"&gt;CourseNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notify_course_participants_on_new_discussion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;course_home_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CourseNotifier is the class where we put our application specific logic on notifications. Note that MailgunGateway is injected in as the default gateway - this is from when we used to have multiple email service providers for campaigning, lists and transactional emails. It is less of a need now that we consolidated all email delivery needs to Mailgun!&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CourseNotifier&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:gateway&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;MailgunGateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@course&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;course&lt;/span&gt;
    &lt;span class="vi"&gt;@gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_course_participants_on_new_discussion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_batch_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;notification_recipients&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="n"&gt;notification_subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;discussion_notification_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;reply_to: &lt;/span&gt;&lt;span class="n"&gt;reply_to_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;recipient_variables: &lt;/span&gt;&lt;span class="n"&gt;recipient_variables&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;notification_recipients&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notification_recipients&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;participants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notification_subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="s2"&gt;"[Tealeaf Academy] &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Posted a New Message on the Discussion Board"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;"[Tealeaf Academy] &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Replied to a Message on the Discussion Board"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reply_to_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s2"&gt;"reply+&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@messaging.gotealeaf.com"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recipient_variables&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipients&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recipients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discussion_notification_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EMAIL&lt;/span&gt;&lt;span class="sh"&gt;
&amp;lt;HTML&amp;gt;&amp;lt;body&amp;gt;
Hi %recipient.name%,

&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; says on the course dicussion board:&amp;lt;/p&amp;gt;

"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;discussion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
&amp;lt;br/&amp;gt;
&amp;lt;p&amp;gt;Reply to this email directly or &amp;lt;a href="http://www.gotealeaf.com/courses/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/home"&amp;gt;view it on the discussion board&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;EMAIL&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reply_to_address is where we insert the post token into the “Reply-To” header. The content of the emails are quite simple so we just put them here in the class. If we had a more elaborate email style, we would have used template rendering to handle it. With Mailgun’s send_batch_message API, we can call the API just once to send to multiple recipients, and the recipient_variables method is where we customize email messages for each receiver to include their names to add a personal touch.&lt;/p&gt;
&lt;h3 id="Handling inbounding messages in the application"&gt;Handling inbounding messages in the application&lt;/h3&gt;
&lt;p&gt;Heading over to Mailgun, under “Routes” in the Control Panel, we created a route as the following:&lt;/p&gt;

&lt;p&gt;Filter Expression: match_recipient(“reply+(.*)&lt;a href="/messaging.gotealeaf." class="user-mention" title="@messaging.gotealeaf."&gt;&lt;i&gt;@&lt;/i&gt;messaging.gotealeaf.&lt;/a&gt;com”)
Action: forward(“&lt;a href="http://www.gotealeaf.com/api/incoming_messages/?post_token=1%E2%80%9D" rel="nofollow" target="_blank"&gt;http://www.gotealeaf.com/api/incoming_messages/?post_token=1”&lt;/a&gt;\)
When a user replies to an email, they reply it to an email address such as “reply+fj42gq4v@messaging.gotealeaf.com”, and this route will forward the email to a web hook that we expose to handle incoming messages.&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::IncomingMessagesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;skip_before_filter&lt;/span&gt; &lt;span class="ss"&gt;:verify_authenticity_token&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sender'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'post_token'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"stripped-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;CourseNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;course&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notify_course_participants_on_new_discussion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we use the sender’s email to find the author, and use the post token to find the post that this reply should be collated under. Mailgun gives us the very useful stripped text which strips away the original message part to only contain the actual reply! In the end, we return a 200 header to tell Mailgun that this interaction is successful, otherwise Mailgun will think our server is down and will faithfully keep trying to call our webhook.&lt;/p&gt;

&lt;p&gt;The result from implementing this workflow is impressive - activity on our discussion board increased three fold, and questions are now typically getting answered within an hour, sometimes even minutes, students are able to move on the next set of tasks a lot quicker and we are very happy how this turned out.&lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Fri, 18 Jan 2013 01:36:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/8155</link>
      <guid>https://ruby-china.org/topics/8155</guid>
    </item>
    <item>
      <title>Tealeaf Academy 推出网上 Ruby on Rails 的训练营</title>
      <description>&lt;p&gt;关注我们的同学会已经发现我们在新年的时候正式上线了 &lt;a href="http://www.gotealeaf.com" rel="nofollow" target="_blank" title=""&gt;Tealaef Academy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这是我们以前做的 Rails Tutors 的一个转型，课程从原来的一个发展到现在的三个 -  Introduction to Ruby and Web Development, Rapid Prototypinng with Ruby on Rails 和 Build Production Quality Applications。 &lt;/p&gt;

&lt;p&gt;新增的两个课程一个会更详细的给新手讲解 Ruby 和互联网应用的编程基础，另一个会是我们的旗舰课程，详细的讲解 Rails 的里面的一些高级概念，测试基础，应用集成，用户管理，电子商务，以及服务订阅类应用的流程。这是一个八周的课程，在这个课程中我们会带领大家做一个 Netflix 网站的克隆。出了大块的概念讲解，练习和实战外，课程里面还有很多实际开发以及生产上线方面的内容，比如敏捷开发，多机部署，生产环境数据维护，备份等等。我自己的感觉是现在网上很多针对新手的教程，但是很少有能帮助从新手到准中级的材料。开源项目大多是  gem / library 型的，而整体项目的要么代码质量不高，要么过于复杂。自己靠摸索掌握这些会走很多弯路。&lt;/p&gt;

&lt;p&gt;学员来自全球，全程英文授课。请大家多提宝贵意见。 &lt;/p&gt;</description>
      <author>knwang</author>
      <pubDate>Mon, 14 Jan 2013 10:03:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/8044</link>
      <guid>https://ruby-china.org/topics/8044</guid>
    </item>
  </channel>
</rss>
