diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
new file mode 100644
index 0000000000..5c3861200e
--- /dev/null
+++ b/cores/esp8266/Schedule.cpp
@@ -0,0 +1,97 @@
+#include "Schedule.h"
+
+struct scheduled_fn_t
+{
+    scheduled_fn_t* mNext;
+    std::function<void(void)> mFunc;
+};
+
+static scheduled_fn_t* sFirst = 0;
+static scheduled_fn_t* sLast = 0;
+
+static scheduled_fn_t* sFirstUnused = 0;
+static scheduled_fn_t* sLastUnused = 0;
+
+static int sCount = 0;
+
+static void init_lists()
+{
+    if (sCount != 0) {
+        return;
+    }
+    while (sCount < SCHEDULED_FN_INITIAL_COUNT) {
+        scheduled_fn_t* it = new scheduled_fn_t;
+        if (sCount == 0) {
+            sFirstUnused = it;
+        }
+        else {
+            sLastUnused->mNext = it;
+        }
+        sLastUnused = it;
+        ++sCount;
+    }
+    sLastUnused->mNext = NULL;
+}
+
+static scheduled_fn_t* get_fn() {
+    scheduled_fn_t* result = NULL;
+    // try to get an item from unused items list
+    if (sFirstUnused) {
+        result = sFirstUnused;
+        sFirstUnused = result->mNext;
+        if (sFirstUnused == NULL) {
+            sLastUnused = NULL;
+        }
+    }
+    // if no unused items, and count not too high, allocate a new one
+    else if (sCount != SCHEDULED_FN_MAX_COUNT) {
+        result = new scheduled_fn_t;
+        result->mNext = NULL;
+        ++sCount;
+    }
+    return result;
+}
+
+static void recycle_fn(scheduled_fn_t* fn)
+{
+    if (!sLastUnused) {
+        sFirstUnused = fn;
+    }
+    else {
+        sLastUnused->mNext = fn;
+    }
+    fn->mNext = NULL;
+    sLastUnused = fn;
+}
+
+bool schedule_function(std::function<void(void)> fn)
+{
+    scheduled_fn_t* item = get_fn();
+    if (!item) {
+        return false;
+    }
+    item->mFunc = fn;
+    item->mNext = NULL;
+    if (!sFirst) {
+        sFirst = item;
+    }
+    else {
+        sLast->mNext = item;
+    }
+    sLast = item;
+    return true;
+}
+
+void run_scheduled_functions()
+{
+    while (sFirst) {
+        scheduled_fn_t* item = sFirst;
+        sFirst = item->mNext;
+        if (sFirst == NULL) {
+            sLast = NULL;
+        }
+        item->mFunc();
+        item->mFunc = std::function<void(void)>();
+        recycle_fn(item);
+    }
+}
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
new file mode 100644
index 0000000000..3399972945
--- /dev/null
+++ b/cores/esp8266/Schedule.h
@@ -0,0 +1,27 @@
+#ifndef ESP_SCHEDULE_H
+#define ESP_SCHEDULE_H
+
+#include <functional>
+
+#define SCHEDULED_FN_MAX_COUNT 32
+#define SCHEDULED_FN_INITIAL_COUNT 4
+
+// Warning 
+// This API is not considered stable. 
+// Function signatures will change.
+// You have been warned.
+
+// Run given function next time `loop` function returns, 
+// or `run_scheduled_functions` is called.
+// Use std::bind to pass arguments to a function, or call a class member function.
+// Note: there is no mechanism for cancelling scheduled functions.
+// Keep that in mind when binding functions to objects which may have short lifetime.
+// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT.
+bool schedule_function(std::function<void(void)> fn);
+
+// Run all scheduled functions. 
+// Use this function if your are not using `loop`, or `loop` does not return
+// on a regular basis.
+void run_scheduled_functions();
+
+#endif //ESP_SCHEDULE_H
diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp
index 634eb7902b..13bb645e25 100644
--- a/cores/esp8266/core_esp8266_main.cpp
+++ b/cores/esp8266/core_esp8266_main.cpp
@@ -23,6 +23,7 @@
 //This may be used to change user task stack size:
 //#define CONT_STACKSIZE 4096
 #include <Arduino.h>
+#include "Schedule.h"
 extern "C" {
 #include "ets_sys.h"
 #include "os_type.h"
@@ -119,6 +120,7 @@ static void loop_wrapper() {
         setup_done = true;
     }
     loop();
+    run_scheduled_functions();
     esp_schedule();
 }
 
diff --git a/tests/device/test_schedule/test_schedule.ino b/tests/device/test_schedule/test_schedule.ino
new file mode 100644
index 0000000000..812d3339b2
--- /dev/null
+++ b/tests/device/test_schedule/test_schedule.ino
@@ -0,0 +1,77 @@
+#include <BSTest.h>
+#include <test_config.h>
+#include <Schedule.h>
+
+BS_ENV_DECLARE();
+
+void setup()
+{
+    Serial.begin(115200);
+    BS_RUN(Serial);
+}
+
+
+TEST_CASE("scheduled functions are executed", "[schedule]")
+{
+    bool executed = false;
+    CHECK(schedule_function([&](){
+        executed = true;
+    }));
+    run_scheduled_functions();
+    CHECK(executed);
+}
+
+TEST_CASE("scheduled functions are executed in correct order", "[schedule]")
+{
+    int counter = 0;
+    auto fn = [&](int id) {
+        CHECK(id == counter);
+        ++counter;
+    };
+    for (int i = 0; i < 8; ++i) {
+        schedule_function(std::bind(fn, i));
+    }
+    run_scheduled_functions();
+}
+
+TEST_CASE("functions are only executed once", "[schedule]")
+{
+    int counter = 0;
+    auto fn = [&](){
+        ++counter;
+    };
+    schedule_function(fn);
+    schedule_function(fn);
+    schedule_function(fn);
+    run_scheduled_functions();
+    CHECK(counter == 3);
+    counter = 0;
+    run_scheduled_functions();
+    CHECK(counter == 0);
+}
+
+TEST_CASE("can schedule SCHEDULED_FN_MAX_COUNT functions", "[schedule]")
+{
+    int counter = 0;
+    auto fn = [&](int id) {
+        CHECK(id == counter);
+        CHECK(id < SCHEDULED_FN_MAX_COUNT);
+        ++counter;
+    };
+    int i;
+    for (i = 0; i < SCHEDULED_FN_MAX_COUNT; ++i) {
+        CHECK(schedule_function(std::bind(fn, i)));
+    }
+    CHECK(!schedule_function(std::bind(fn, i)));
+    run_scheduled_functions();
+    CHECK(counter == SCHEDULED_FN_MAX_COUNT);
+    counter = 0;
+    for (i = 0; i < SCHEDULED_FN_MAX_COUNT; ++i) {
+        CHECK(schedule_function(std::bind(fn, i)));
+    }
+    CHECK(!schedule_function(std::bind(fn, i)));
+    run_scheduled_functions();
+    CHECK(counter == SCHEDULED_FN_MAX_COUNT);
+}
+
+void loop(){}