diff --git a/integrations/asana/zulip_asana_config.py b/integrations/asana/zulip_asana_config.py
deleted file mode 100644
index bfe9fd7f..00000000
--- a/integrations/asana/zulip_asana_config.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright © 2014 Zulip, Inc.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-
-### REQUIRED CONFIGURATION ###
-
-# Change these values to your Asana credentials.
-ASANA_API_KEY = "0123456789abcdef0123456789abcdef"
-
-# Change these values to the credentials for your Asana bot.
-ZULIP_USER = "asana-bot@example.com"
-ZULIP_API_KEY = "0123456789abcdef0123456789abcdef"
-
-# The Zulip stream that will receive Asana task updates.
-ZULIP_STREAM_NAME = "asana"
-
-
-### OPTIONAL CONFIGURATION ###
-
-# Set to None for logging to stdout when testing, and to a file for
-# logging in production.
-#LOG_FILE = "/var/tmp/zulip_asana.log"
-LOG_FILE = None
-
-# This file is used to resume this mirror in case the script shuts down.
-# It is required and needs to be writeable.
-RESUME_FILE = "/var/tmp/zulip_asana.state"
-
-# When initially started, how many hours of messages to include.
-ASANA_INITIAL_HISTORY_HOURS = 1
-
-# Set this to your Zulip API server URI
-ZULIP_SITE = "https://zulip.example.com"
-
-# If properly installed, the Zulip API should be in your import
-# path, but if not, set a custom path below
-ZULIP_API_PATH = None
diff --git a/integrations/asana/zulip_asana_mirror b/integrations/asana/zulip_asana_mirror
deleted file mode 100755
index e15436ab..00000000
--- a/integrations/asana/zulip_asana_mirror
+++ /dev/null
@@ -1,306 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Asana integration for Zulip
-#
-# Copyright © 2014 Zulip, Inc.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# The "zulip_asana_mirror" script is run continuously, possibly on a work computer
-# or preferably on a server.
-#
-# When restarted, it will attempt to pick up where it left off.
-#
-# python-dateutil is a dependency for this script.
-
-from __future__ import print_function
-import base64
-from datetime import datetime, timedelta
-from typing import List, Dict, Optional, Any, Tuple
-
-import json
-import logging
-import os
-import time
-from six.moves import urllib
-from six.moves.urllib import request as urllib_request
-import sys
-
-
-try:
-    import dateutil.parser
-    from dateutil.tz import gettz
-except ImportError as e:
-    print(e, file=sys.stderr)
-    print("Please install the python-dateutil package.", file=sys.stderr)
-    exit(1)
-
-sys.path.insert(0, os.path.dirname(__file__))
-import zulip_asana_config as config
-VERSION = "0.9"
-
-if config.ZULIP_API_PATH is not None:
-    sys.path.append(config.ZULIP_API_PATH)
-import zulip
-
-if config.LOG_FILE:
-    logging.basicConfig(filename=config.LOG_FILE, level=logging.WARNING)
-else:
-    logging.basicConfig(level=logging.INFO)
-
-client = zulip.Client(email=config.ZULIP_USER, api_key=config.ZULIP_API_KEY,
-                      site=config.ZULIP_SITE, client="ZulipAsana/" + VERSION)
-
-def fetch_from_asana(path):
-    # type: (str) -> Optional[Dict[str, Any]]
-    """
-    Request a resource through the Asana API, authenticating using
-    HTTP basic auth.
-    """
-    auth = base64.encodestring(b'%s:' % (config.ASANA_API_KEY,))
-    headers = {"Authorization": "Basic %s" % auth}
-
-    url = "https://app.asana.com/api/1.0" + path
-    request = urllib_request.Request(url, None, headers) # type: ignore
-    result = urllib_request.urlopen(request) # type: ignore
-
-    return json.load(result)
-
-def send_zulip(topic, content):
-    # type: (str, str) -> Dict[str, str]
-    """
-    Send a message to Zulip using the configured stream and bot credentials.
-    """
-    message = {"type": "stream",
-               "sender": config.ZULIP_USER,
-               "to": config.ZULIP_STREAM_NAME,
-               "subject": topic,
-               "content": content,
-               }
-    return client.send_message(message)
-
-def datestring_to_datetime(datestring):
-    # type: (str) -> datetime
-    """
-    Given an ISO 8601 datestring, return the corresponding datetime object.
-    """
-    return dateutil.parser.parse(datestring).replace(
-        tzinfo=gettz('Z'))
-
-class TaskDict(dict):
-    """
-    A helper class to turn a dictionary with task information into an
-    object where each of the keys is an attribute for easy access.
-    """
-    def __getattr__(self, field):
-        # type: (TaskDict, str) -> Any
-        return self.get(field)
-
-def format_topic(task, projects):
-    # type: (TaskDict, Dict[str, str]) -> str
-    """
-    Return a string that will be the Zulip message topic for this task.
-    """
-    # Tasks can be associated with multiple projects, but in practice they seem
-    # to mostly be associated with one.
-    project_name = projects[task.projects[0]["id"]]
-    return "%s: %s" % (project_name, task.name)
-
-def format_assignee(task, users):
-    # type: (TaskDict, Dict[str, str]) -> str
-    """
-    Return a string describing the task's assignee.
-    """
-    if task.assignee:
-        assignee_name = users[task.assignee["id"]]
-        assignee_info = "**Assigned to**: %s (%s)" % (
-            assignee_name, task.assignee_status)
-    else:
-        assignee_info = "**Status**: Unassigned"
-
-    return assignee_info
-
-def format_due_date(task):
-    # type: (TaskDict) -> str
-    """
-    Return a string describing the task's due date.
-    """
-    if task.due_on:
-        due_date_info = "**Due on**: %s" % (task.due_on,)
-    else:
-        due_date_info = "**Due date**: None"
-    return due_date_info
-
-def format_task_creation_event(task, projects, users):
-    # type: (TaskDict, Dict[str, str], Dict[str, str]) -> Tuple[str, str]
-    """
-    Format the topic and content for a newly-created task.
-    """
-    topic = format_topic(task, projects)
-    assignee_info = format_assignee(task, users)
-    due_date_info = format_due_date(task)
-
-    content = """Task **%s** created:
-
-~~~ quote
-%s
-~~~
-
-%s
-%s
-""" % (task.name, task.notes, assignee_info, due_date_info)
-    return topic, content
-
-def format_task_completion_event(task, projects, users):
-    # type: (TaskDict, Dict[str, str], Dict[str, str]) -> Tuple[str, str]
-    """
-    Format the topic and content for a completed task.
-    """
-    topic = format_topic(task, projects)
-    assignee_info = format_assignee(task, users)
-    due_date_info = format_due_date(task)
-
-    content = """Task **%s** completed. :white_check_mark:
-
-%s
-%s
-""" % (task.name, assignee_info, due_date_info)
-    return topic, content
-
-def since():
-    # type: () -> datetime
-    """
-    Return a newness threshold for task events to be processed.
-    """
-    # If we have a record of the last event processed and it is recent, use it,
-    # else process everything from ASANA_INITIAL_HISTORY_HOURS ago.
-    def default_since():
-        # type: () -> datetime
-        return datetime.utcnow() - timedelta(
-            hours=config.ASANA_INITIAL_HISTORY_HOURS)
-
-    if os.path.exists(config.RESUME_FILE):
-        try:
-            with open(config.RESUME_FILE, "r") as f:
-                datestring = f.readline().strip()
-                timestamp = float(datestring)
-                max_timestamp_processed = datetime.fromtimestamp(timestamp)
-                logging.info("Reading from resume file: " + datestring)
-        except (ValueError, IOError) as e:
-            logging.warn("Could not open resume file: " + str(e))
-            max_timestamp_processed = default_since()
-    else:
-        logging.info("No resume file, processing an initial history.")
-        max_timestamp_processed = default_since()
-
-    # Even if we can read a timestamp from RESUME_FILE, if it is old don't use
-    # it.
-    return max(max_timestamp_processed, default_since())
-
-def process_new_events():
-    # type: () -> None
-    """
-    Forward new Asana task events to Zulip.
-    """
-    # In task queries, Asana only exposes IDs for projects and users, so we need
-    # to look up the mappings.
-    projects = dict((elt["id"], elt["name"]) for elt in
-                    fetch_from_asana("/projects")["data"])
-    users = dict((elt["id"], elt["name"]) for elt in
-                 fetch_from_asana("/users")["data"])
-
-    cutoff = since()
-    max_timestamp_processed = cutoff
-    time_operations = (("created_at", format_task_creation_event),
-                       ("completed_at", format_task_completion_event))
-    task_fields = ["assignee", "assignee_status", "created_at", "completed_at",
-                   "modified_at", "due_on", "name", "notes", "projects"]
-
-    # First, gather all of the tasks that need processing. We'll
-    # process them in order.
-    new_events = []
-
-    for project_id in projects:
-        project_url = "/projects/%d/tasks?opt_fields=%s" % (
-            project_id, ",".join(task_fields))
-        tasks = fetch_from_asana(project_url)["data"]
-
-        for task in tasks:
-            task = TaskDict(task)
-
-            for time_field, operation in time_operations:
-                if task[time_field]:
-                    operation_time = datestring_to_datetime(task[time_field])
-                    if operation_time > cutoff:
-                        new_events.append((operation_time, time_field, operation, task))
-
-    new_events.sort()
-    now = datetime.utcnow()
-
-    for operation_time, time_field, operation, task in new_events:
-        # Unfortunately, creating an Asana task is not an atomic operation. If
-        # the task was just created, or is missing basic information, it is
-        # probably because the task is still being filled out -- wait until the
-        # next round to process it.
-        if (time_field == "created_at") and \
-                (now - operation_time < timedelta(seconds=30)):
-            # The task was just created, give the user some time to fill out
-            # more information.
-            return
-
-        if (time_field == "created_at") and (not task.name) and \
-                (now - operation_time < timedelta(seconds=60)):
-            # If this new task hasn't had a name for a full 30 seconds, assume
-            # you don't plan on giving it one.
-            return
-
-        topic, content = operation(task, projects, users)
-        logging.info("Sending Zulip for " + topic)
-        result = send_zulip(topic, content)
-
-        # If the Zulip wasn't sent successfully, don't update the
-        # max timestamp processed so the task has another change to
-        # be forwarded. Exit, giving temporary issues time to
-        # resolve.
-        if not result.get("result"):
-            logging.warn("Malformed result, exiting:")
-            logging.warn(str(result))
-            return
-
-        if result["result"] != "success":
-            logging.warn(result["msg"])
-            return
-
-        if operation_time > max_timestamp_processed:
-            max_timestamp_processed = operation_time
-
-    if max_timestamp_processed > cutoff:
-        max_datestring = max_timestamp_processed.strftime("%s.%f")
-        logging.info("Updating resume file: " + max_datestring)
-        open(config.RESUME_FILE, 'w').write(max_datestring)
-
-while True:
-    try:
-        process_new_events()
-        time.sleep(5)
-    except KeyboardInterrupt:
-        logging.info("Shutting down...")
-        logging.info("Set LOG_FILE to log to a file instead of stdout.")
-        break