diff --git a/tools/run-mypy b/tools/run-mypy
index 5b33910c..fceb4fc3 100755
--- a/tools/run-mypy
+++ b/tools/run-mypy
@@ -69,6 +69,8 @@ force_include = [
     "zulip_bots/zulip_bots/bots/wikipedia/test_wikipedia.py",
     "zulip_bots/zulip_bots/bots/yoda/yoda.py",
     "zulip_bots/zulip_bots/bots/yoda/test_yoda.py",
+    "zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py",
+    "zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py"
 ]
 
 parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")
diff --git a/zulip_bots/setup.py b/zulip_bots/setup.py
index f372aa80..03eb9a19 100755
--- a/zulip_bots/setup.py
+++ b/zulip_bots/setup.py
@@ -53,7 +53,8 @@ setuptools_info = dict(
         'BeautifulSoup4',  # for bots/googlesearch
         'lxml',  # for bots/googlesearch
         'requests',  # for bots/link_shortener
-        'python-chess[engine,gaviota]'  # for bots/chess
+        'python-chess[engine,gaviota]',  # for bots/chess
+        'apiai'  # for bots/dialogflow
     ],
 )
 
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/__init__.py b/zulip_bots/zulip_bots/bots/dialogflow/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/dialogflow.conf b/zulip_bots/zulip_bots/bots/dialogflow/dialogflow.conf
new file mode 100644
index 00000000..5de662cd
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/dialogflow.conf
@@ -0,0 +1,3 @@
+[dialogflow]
+key=YOUR_PUBLIC_API_KEY_HERE
+bot_info=BOT_INFO_HERE
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py b/zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py
new file mode 100644
index 00000000..c3838b76
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py
@@ -0,0 +1,57 @@
+# See readme.md for instructions on running this code.
+from __future__ import print_function
+import logging
+from six.moves.urllib import parse
+import json
+
+import apiai
+
+from typing import Dict, Any, List
+
+help_message = '''DialogFlow bot
+This bot will interact with dialogflow bots.
+Simply send this bot a message, and it will respond depending on the configured bot's behaviour.
+'''
+
+def get_bot_result(message_content: str, config: Dict[str, str], sender_id: str) -> str:
+    if message_content.strip() == '' or message_content.strip() == 'help':
+        return config['bot_info']
+    ai = apiai.ApiAI(config['key'])
+    try:
+        request = ai.text_request()
+        request.session_id = sender_id
+        request.query = message_content
+        response = request.getresponse()
+        res_str = response.read().decode('utf8', 'ignore')
+        res_json = json.loads(res_str)
+        if res_json['status']['errorType'] != 'success':
+            return 'Error {}: {}.'.format(res_json['status']['code'], res_json['status']['errorDetails'])
+        if res_json['result']['fulfillment']['speech'] == '':
+            if res_json['alternateResult']['fulfillment']['speech'] == '':
+                return 'Error. No result.'
+            return res_json['alternateResult']['fulfillment']['speech']
+        return res_json['result']['fulfillment']['speech']
+    except Exception as e:
+        logging.exception(str(e))
+        return 'Error. {}.'.format(str(e))
+
+class DialogFlowHandler(object):
+    '''
+    This plugin allows users to easily add their own
+    DialogFlow bots to zulip
+    '''
+
+    def initialize(self, bot_handler: Any) -> None:
+        self.config_info = bot_handler.get_config_info('dialogflow')
+
+    def usage(self) -> str:
+        return '''
+            This plugin will allow users to easily add their own
+            DialogFlow bots to zulip
+            '''
+
+    def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
+        result = get_bot_result(message['content'], self.config_info, message['sender_id'])
+        bot_handler.send_reply(message, result)
+
+handler_class = DialogFlowHandler
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/doc.md b/zulip_bots/zulip_bots/bots/dialogflow/doc.md
new file mode 100644
index 00000000..d4a0c28f
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/doc.md
@@ -0,0 +1,24 @@
+# DialogFlow bot
+This bot allows users to easily add their own DialogFlow bots to zulip.
+
+## Setup
+To add your DialogFlow bot:
+Add the V1 Client access token from your agent's settings in the DialogFlow console to
+`dialogflow.conf`, and write a short sentence describing what your bot does in the same file
+as `bot_info`.
+
+## Usage
+
+Run this bot as described
+[here](https://zulipchat.com/api/running-bots#running-a-bot).
+
+Mention the bot in order to say things to it.
+
+For example: `@weather What is the weather today?`
+
+
+## Limitations
+When creating your DialogFlow bot, please consider these things:
+
+- Empty input will not be sent to the bot.
+- Only text can be sent to, and recieved from the bot.
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_403.json b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_403.json
new file mode 100644
index 00000000..c34201ee
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_403.json
@@ -0,0 +1,12 @@
+{
+    "response": {
+      "status": {
+        "errorType": "fail",
+        "code": "403",
+        "errorDetails": "Access Denied"
+      }
+    },
+    "request": {
+      "query": "hello"
+    }
+  }
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_alternate_result.json b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_alternate_result.json
new file mode 100644
index 00000000..eb867e59
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_alternate_result.json
@@ -0,0 +1,20 @@
+{
+    "response": {
+      "result": {
+        "fulfillment": {
+          "speech": ""
+        }
+      },
+      "alternateResult": {
+        "fulfillment": {
+            "speech": "alternate result"
+        }
+      },
+      "status": {
+        "errorType": "success"
+      }
+    },
+    "request": {
+      "query": "hello"
+    }
+  }
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_empty_response.json b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_empty_response.json
new file mode 100644
index 00000000..8982a1b0
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_empty_response.json
@@ -0,0 +1,20 @@
+{
+    "response": {
+      "result": {
+        "fulfillment": {
+          "speech": ""
+        }
+      },
+      "alternateResult": {
+        "fulfillment": {
+            "speech": ""
+        }
+      },
+      "status": {
+        "errorType": "success"
+      }
+    },
+    "request": {
+      "query": "hello"
+    }
+  }
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_exception.json b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_exception.json
new file mode 100644
index 00000000..ca74e826
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_exception.json
@@ -0,0 +1,15 @@
+{
+    "response": {
+      "foo": {
+        "fulfillment": {
+          "speech": "how are you?"
+        }
+      },
+      "bar": {
+        "errorType": "success"
+      }
+    },
+    "request": {
+      "query": "hello"
+    }
+  }
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_normal.json b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_normal.json
new file mode 100644
index 00000000..ae076333
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_normal.json
@@ -0,0 +1,15 @@
+{
+  "response": {
+    "result": {
+      "fulfillment": {
+        "speech": "how are you?"
+      }
+    },
+    "status": {
+      "errorType": "success"
+    }
+  },
+  "request": {
+    "query": "hello"
+  }
+}
diff --git a/zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py b/zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py
new file mode 100644
index 00000000..f7ddc4b4
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+from zulip_bots.test_lib import StubBotTestCase, read_bot_fixture_data
+
+from contextlib import contextmanager
+
+from unittest.mock import patch
+
+from typing import Any, ByteString
+
+import json
+
+class MockTextRequest():
+    def __init__(self) -> None:
+        self.session_id = ""
+        self.query = ""
+        self.response = ""
+
+    def getresponse(self) -> Any:
+        return MockHttplibRequest(self.response)
+
+class MockHttplibRequest():
+    def __init__(self, response: str) -> None:
+        self.response = response
+
+    def read(self) -> ByteString:
+        return json.dumps(self.response).encode()
+
+@contextmanager
+def mock_dialogflow(test_name: str, bot_name: str) -> Any:
+    response_data = read_bot_fixture_data(bot_name, test_name)
+    df_request = response_data.get('request')
+    df_response = response_data.get('response')
+
+    with patch('apiai.ApiAI.text_request') as mock_text_request:
+        request = MockTextRequest()
+        request.response = df_response
+        mock_text_request.return_value = request
+        yield
+
+class TestDialogFlowBot(StubBotTestCase):
+    bot_name = 'dialogflow'
+
+    def _test(self, test_name: str, message: str, response: str) -> None:
+        with self.mock_config_info({'key': 'abcdefg', 'bot_info': 'bot info foo bar'}), \
+                mock_dialogflow(test_name, 'dialogflow'):
+            self.verify_reply(message, response)
+
+    def test_normal(self) -> None:
+        self._test('test_normal', 'hello', 'how are you?')
+
+    def test_403(self) -> None:
+        self._test('test_403', 'hello', 'Error 403: Access Denied.')
+
+    def test_empty_response(self) -> None:
+        self._test('test_empty_response', 'hello', 'Error. No result.')
+
+    def test_exception(self) -> None:
+        with patch('logging.exception'):
+            self._test('test_exception', 'hello', 'Error. \'status\'.')
+
+    def test_help(self) -> None:
+        self._test('test_normal', 'help', 'bot info foo bar')
+        self._test('test_normal', '', 'bot info foo bar')
+
+    def test_alternate_response(self) -> None:
+        self._test('test_alternate_result', 'hello', 'alternate result')
+
+    def test_bot_responds_to_empty_message(self) -> None:
+        with self.mock_config_info({'key': 'abcdefg', 'bot_info': 'bot info foo bar'}):
+            pass
diff --git a/zulip_bots/zulip_bots/test_lib.py b/zulip_bots/zulip_bots/test_lib.py
index f537a6d4..5625b94d 100755
--- a/zulip_bots/zulip_bots/test_lib.py
+++ b/zulip_bots/zulip_bots/test_lib.py
@@ -114,6 +114,7 @@ class StubBotTestCase(TestCase):
             display_recipient='foo_stream',
             sender_email='foo@example.com',
             sender_full_name='Foo Test User',
+            sender_id='123',
             content=content,
         )
         return message