diff --git a/RWTH Misc.ipynb b/RWTH Misc.ipynb
index 78de047c02547ae8720598c2fd04e9ba6647c301..6568c4a0941f5f18bc6a3d9deef07804862f971c 100644
--- a/RWTH Misc.ipynb	
+++ b/RWTH Misc.ipynb	
@@ -25,14 +25,14 @@
     "\n",
     "mail_to = \"example@rwth-aachen.de\" # send feedback via mail\n",
     "feedback_name = rwth_feedback.get_notebook_name() # get name of notebook automatically\n",
-    "rwth_feedback.rwth_feedback(feedback_name, [\n",
+    "rwth_feedback.RWTHFeedback(feedback_name, [\n",
     "    {'id': 'likes', 'type': 'free-text', 'label': 'Das war gut:'}, \n",
     "    {'id': 'dislikes', 'type': 'free-text', 'label': 'Das könnte verbessert werden:'}, \n",
     "    {'id': 'misc', 'type': 'free-text', 'label': 'Was ich sonst noch sagen möchte:'}, \n",
     "    {'id': 'learning', 'type': 'scale', 'label' : 'Ich habe das Gefühl etwas gelernt zu haben.'},\n",
     "    {'id': 'supervision', 'type': 'scale', 'label' : 'Die Betreuung des Versuchs war gut.'},\n",
     "    {'id': 'script', 'type': 'scale', 'label' : 'Die Versuchsunterlagen sind verständlich.'},\n",
-    "], \"feedback.json\", mail_to)"
+    "], \"feedback.json\", mail_to);"
    ]
   },
   {
diff --git a/rwth_nb/misc/feedback.py b/rwth_nb/misc/feedback.py
index d2a8834b0c3cbfe544fb8acd1439fa727607b725..1f23b2b78eab3cb6ad749c36f8049078625d3f63 100644
--- a/rwth_nb/misc/feedback.py
+++ b/rwth_nb/misc/feedback.py
@@ -35,144 +35,315 @@ def get_notebook_name():
         return 'tmp'
 
 
-def rwth_feedback(feedback_name, questions, feedback_path='feedback.json', mail_to=None, mail_from='feedback@jupyter.rwth-aachen.de', mail_subject=None, mail_smtp_host='smarthost.rwth-aachen.de'):
-    global widgets_container
-    is_feedback_edited = is_send_confirmed = False
+class RWTHFeedback():
+    """
+    RWTH Feedback submission class
 
-    def on_feedback_edited(_):
-        nonlocal is_feedback_edited
-        is_feedback_edited = True
+    Use as described in RWTH\ Misc.ipynb
+    """
+    is_send_confirmed = is_saved_locally = is_mail_sent = False
 
     # Internationalization
-    feedback_scale_options_de = ['Stimme voll zu', 'Ich stimme zu', 'Keine Meinung', 'Ich stimme nicht zu', 'Ich stimme gar nicht zu'] # Likert-scale
-    feedback_text_de = {"your-feedback" : "Dein Feedback ...",
-                    "send" : "Absenden",
-                    "confirm_send" : "Zum Absenden bitte bestätigen.",
-                    "sent" : "Dein Feedback wurde abgeschickt. Vielen Dank!",
-                    "empty" : "Bitte geben Sie zuerst Feedback ein, das abgesendet werden kann.",
-                    "mailfailure": "Die Mail mit dem Feedback konnte nicht versendet werden. Das Feedback wurde lokal abgespeichert."}
+    feedback_scale_options_de = ['Stimme voll zu', 'Ich stimme zu', 'Keine Meinung', 'Ich stimme nicht zu',
+                                 'Ich stimme gar nicht zu']  # Likert-scale
+    feedback_text_de = {"your-feedback": "Dein Feedback ...",
+                        "send": "Abschicken",
+                        "confirm_send": "Abschicken bestätigen.",
+                        "sent": "Dein Feedback wurde abgeschickt. Vielen Dank!",
+                        "empty": "Bitte geben Sie zuerst Feedback ein, das abgesendet werden kann.",
+                        "mailfailure": "Die Mail mit dem Feedback konnte nicht versendet werden. Das Feedback wurde lokal abgespeichert."}
     # TODO: Add at least English here
-    
+
     # Select language
-    feedback_scale_options = feedback_scale_options_de; feedback_text = feedback_text_de;
-    
+    feedback_scale_options = feedback_scale_options_de;
+    feedback_text = feedback_text_de;
+
     # Default arguments for toggle_button and textarea
-    toggle_args = {"options" : feedback_scale_options, 
-                   "index" : 2,  "description" : "",  "disabled" : False,  "style": {'button_color': rwth_colors['rwth:green-50']},  "tooltips" : []}
-    textarea_args = {"value" : "",  "placeholder" : feedback_text['your-feedback'], 
-                     "description" : "",  "disabled" : False}
-    
-    widgets_container = [];
-    widgets_values = [];
-    for question in questions:
-        if question['type'] == 'free-text':
-             # Free text
-            widget_label = widgets.HTML(value="<b>{}</b>".format(question['label']))
-            widget_value = widgets.Textarea(**textarea_args)
-            widget_value.observe(on_feedback_edited, 'value')
-                        
-        elif question['type'] == 'scale':
-            # Toggle Buttons
-            widget_label = widgets.HTML(value="<b>{}</b>".format(question['label']))
-            widget_value = widgets.ToggleButtons(**toggle_args)
-            widget_value.observe(on_feedback_edited, 'value')
-
-        widgets_container.append(widget_label)
-        widgets_container.append(widget_value)
-        widgets_values.append(widget_value) # TODO: Get rid of this?
-
-    # Button 
-    button = widgets.Button(description = feedback_text['send'], disabled = False, style= {'button_color': rwth_colors['rwth:green-50'], 'margin':'10px'}, icon='', layout=Layout(margin='20px 0 0 0'))
-    output = widgets.Output()
-    
-    # Button click callback
-    def on_button_clicked(b):
-        nonlocal is_send_confirmed
-
-        if not button.disabled:
-            entry = {}
-
-            nonlocal is_feedback_edited
-            if not is_feedback_edited:
-                with output:
-                    print(feedback_text['empty'])    
+    toggle_args = {"options": feedback_scale_options,
+                   "index": 2, "description": "", "disabled": False,
+                   "style": {'button_color': rwth_colors['rwth:green-50']}, "tooltips": []}
+    textarea_args = {"value": "", "placeholder": feedback_text['your-feedback'],
+                     "description": "", "disabled": False}
+
+    def __init__(self, feedback_name, questions, feedback_path='feedback.json', mail_to=None,
+                      mail_from='feedback@jupyter.rwth-aachen.de', mail_subject=None,
+                      mail_smtp_host='smarthost.rwth-aachen.de'):
+        self.feedback_name = feedback_name
+        self.questions = questions
+        self.feedback_path = feedback_path
+        self.mail_to = mail_to
+        self.mail_from = mail_from
+        self.mail_subject = mail_subject
+        self.mail_smtp_host = mail_smtp_host
+
+        self.feedback_status = {
+            'idle': self.ui_state_idle,
+            'saved_locally': self.ui_state_saved_locally,
+            'mail_sent': self.ui_state_mail_sent
+        }
+
+        # self.widgets_container:
+        #   dict containing to each id key as defined in q a widget
+        #   i.e. {'likes': widgets.Textarea, ...}
+        self.widgets_container = {}
+
+        # self.widgets_VBoxes:
+        #   list containing vertical boxes with labels and according widgets
+        #   used for ui design
+        self.widgets_VBoxes = []
+
+        self.entry = {}
+        self.entries = []
+
+        # set up UI
+        self.setup_ui()
+
+    def setup_ui(self):
+        """
+        Set up user interface according to given questions
+        """
+        for question in self.questions:
+            if question['type'] == 'free-text':
+                # Free text
+                widget_label = widgets.HTML(value="<b>{}</b>".format(question['label']))
+                widget_value = widgets.Textarea(**self.textarea_args)
+
+            elif question['type'] == 'scale':
+                # Toggle Buttons
+                widget_label = widgets.HTML(value="<b>{}</b>".format(question['label']))
+                widget_value = widgets.ToggleButtons(**self.toggle_args)
+
+            self.widgets_VBoxes.append(widgets.VBox([widget_label, widget_value]))
+            self.widgets_container[question['id']] = widget_value
+
+        # Button
+        self.btn_submit = widgets.Button(description=self.feedback_text['send'], disabled=False,
+                                         style={'button_color': rwth_colors['rwth:green-50'], 'margin': '10px'}, icon='',
+                                         layout=Layout(margin='20px 0 0 0', width='auto'))
+        self.output = widgets.Output()
+
+        self.btn_submit.on_click(self.on_btn_submit_clicked)
+
+        # Display widgets
+        display(widgets.VBox(self.widgets_VBoxes),
+                self.btn_submit, self.output);
+
+        self.update_ui_state()
+
+    def check_submission_status(self):
+        """
+        Check entry submission status
+
+        Returns
+        -------
+        status: {'idle', 'saved_locally', 'mail_sent'}, str
+            submission status
+            'idle', if feedback does not exist in feedback json file
+            'saved_locally', if feedback exists but was not sent
+            'mail_sent', if feedback was already sent via mail
+        """
+        try:
+            self.load_json_entries()
+            for entry in self.entries:
+                if self.feedback_name == entry['name']:
+                    self.is_saved_locally = True  # Note: at least saved_locally, can also be mail_sent, we don't care
+                    return entry['status']
+            return 'idle'
+        except FileNotFoundError:
+            return 'idle'
+
+    def send_mail(self):
+        """
+        Sends JSON file as attachment of a mail to predefined recipient
+
+        Sets self.is_mail_sent to True if mail was sent successfully. False otherwise.
+        """
+        try:
+            import smtplib
+            from email.mime.multipart import MIMEMultipart
+            from email.mime.base import MIMEBase
+            from email import encoders
+
+            # create message
+            msg = MIMEMultipart()
+            msg['From'] = self.mail_from
+            msg['To'] = self.mail_to
+
+            if self.mail_subject is not None:
+                msg['Subject'] = self.mail_subject
             else:
-                # set up json entries
-                entry['name'] = feedback_name
-                entry['date'] = "{}".format(datetime.datetime.now())
-                if 'USER' in os.environ.keys():
-                    user_name = os.environ['USER']
-                else:
-                    user_name = os.environ['HOSTNAME']  # take hostname as fall-back
-                entry['userhash'] = hashlib.sha256(str.encode(user_name)).hexdigest()
-                entry['answer'] = {q['id']: v.value for q, v in zip(questions, widgets_values)}
-
-                if not is_send_confirmed:
-                    button.description = feedback_text['confirm_send']
-                    button.layout.width = 'auto'
-                    is_send_confirmed = True
-                    return
-
-                # dump entries into json file
-                if not os.path.isfile(feedback_path):
-                    with open(feedback_path, mode='w', encoding='utf-8') as f:
-                        json.dump([], f)
-                with open(feedback_path, mode='r', encoding='utf-8') as f:
-                    entries = json.load(f)
-                with open(feedback_path, mode='w', encoding='utf-8') as f:
-                    entries.append(entry)
-                    json.dump(entries, f)
-
-                # send json file as attachment of mail
-                if mail_to is not None:
-                    try:
-                        import smtplib
-                        from email.mime.multipart import MIMEMultipart
-                        from email.mime.base import MIMEBase
-                        from email import encoders
-                        
-                        # create message                
-                        msg = MIMEMultipart()
-                        msg['From'] = mail_from
-                        msg['To'] = mail_to
-                        
-                        if mail_subject is not None:
-                            msg['Subject'] = mail_subject
-                        else:
-                            msg['Subject'] = feedback_name
-
-                        # open the file to be sent as attachment
-                        with open(feedback_path, 'rb') as attachment:
-                            # attach file to mail
-                            p = MIMEBase('application', 'octet-stream')
-                            p.set_payload(attachment.read())
-
-                        # encode and add as attachment to message
-                        encoders.encode_base64(p)
-                        p.add_header('Content-Disposition', "attachment; filename= %s" % feedback_path)
-                        msg.attach(p)
-
-                        # send mail
-                        s = smtplib.SMTP(mail_smtp_host)
-                        text = msg.as_string()
-                        s.sendmail(mail_from, mail_to, text)
-
-                        # close connection
-                        s.quit()
-
-                        with output:
-                            print(feedback_text['sent'])
-
-                    except ConnectionRefusedError:
-                        # Not connected to the RWTH network
-                        with output:
-                            print(feedback_text['mailfailure'])
-
-                button.disabled = True
-
-    # Set callback to button
-    button.on_click(on_button_clicked)
-
-    # Display widgets
-    display(widgets.VBox(widgets_container), 
-        button, output)
\ No newline at end of file
+                msg['Subject'] = self.feedback_name
+
+            # open the file to be sent as attachment
+            with open(self.feedback_path, 'rb') as attachment:
+                # attach file to mail
+                p = MIMEBase('application', 'octet-stream')
+                p.set_payload(attachment.read())
+
+            # encode and add as attachment to message
+            encoders.encode_base64(p)
+            p.add_header('Content-Disposition', "attachment; filename= %s" % self.feedback_path)
+            msg.attach(p)
+
+            # send mail
+            s = smtplib.SMTP(self.mail_smtp_host)
+            text = msg.as_string()
+            s.sendmail(self.mail_from, self.mail_to, text)
+
+            # close connection
+            s.quit()
+
+            self.is_mail_sent = True
+
+        except ConnectionRefusedError:
+            # Not connected to the RWTH network
+            with self.output:
+                print(self.feedback_text['mailfailure'])
+
+            self.is_mail_sent = False
+
+    def on_btn_submit_clicked(self, _):
+        """
+        Submit button onClick method
+
+        Sets current json entry
+        Calls send_mail method
+        Sets status of all entries to mail_sent if mail was sent successful.
+        Otherwise entries are locally saved.
+        """
+        # set up json entries
+        self.entry['name'] = self.feedback_name
+        self.entry['date'] = "{}".format(datetime.datetime.now())
+        if 'USER' in os.environ.keys():
+            user_name = os.environ['USER']
+        else:
+            user_name = os.environ['HOSTNAME']  # take hostname as fall-back
+        self.entry['userhash'] = hashlib.sha256(str.encode(user_name)).hexdigest()
+        self.entry['answer'] = {key: w.value for key, w in self.widgets_container.items()}
+        self.entry['status'] = 'saved_locally'
+
+        # confirm submission if not happened already
+        if not self.is_send_confirmed:
+            self.btn_submit.description = self.feedback_text['confirm_send']
+            self.btn_submit.layout.width = 'auto'
+            self.is_send_confirmed = True
+            return
+
+        # dump entries into json file
+        # append only if not entry does not already exist in json file
+        # TODO - Editing locally saved submission is not possible. Change?
+        self.load_json_entries()
+        if not self.is_saved_locally:
+            self.entries.append(self.entry)
+        self.save_json_entries()
+
+        # try to send json file as attachment of mail
+        if self.mail_to is not None:
+            self.send_mail()
+
+        # open and set statuses to mail_sent if mail is successfully sent
+        if self.is_mail_sent:
+            self.load_json_entries()
+
+            for entry in self.entries:
+                entry['status'] = 'mail_sent'
+
+            self.save_json_entries()
+
+        self.update_ui_state()
+
+    def save_json_entries(self):
+        """
+        Save JSON entries
+
+        Dumps self.entries into existing file
+        """
+        with open(self.feedback_path, mode='w', encoding='utf-8') as f:
+            json.dump(self.entries, f)
+
+    def load_json_entries(self):
+        """
+        Load JSON entries
+        Creates a new file if non-existent
+
+        Entries are loaded into self.entries
+        """
+        # create JSON file if it does not exist
+        if not os.path.isfile(self.feedback_path):
+            with open(self.feedback_path, mode='w', encoding='utf-8') as f:
+                json.dump([], f)
+
+        # load JSON file to self.entries
+        with open(self.feedback_path, mode='r', encoding='utf-8') as f:
+            self.entries = json.load(f)
+
+    def update_ui_state(self):
+        """
+        Updates UI state according to feedback submission status.
+
+        Calls either:
+        ui_state_idle, ui_state_saved_locally or ui_state_mail_sent
+        according to assignments in self.feedback_status
+        """
+        self.status = self.check_submission_status()
+        self.feedback_status[self.status]()
+
+    def update_btn_state(self):
+        """
+        Updates button UI state
+
+        Used for confirmation requirement for submitting.
+        """
+        if not self.is_send_confirmed:
+            self.btn_submit.description = self.feedback_text['confirm_send']
+        else:
+            self.btn_submit.description = self.feedback_text['send']
+
+    def ui_state_idle(self):
+        """
+        Sets UI state to idle
+        # all free-text and scales are left untouched
+        # submit button enabled
+        """
+        pass
+
+    def ui_state_saved_locally(self):
+        """
+        Sets UI state to saved_locally
+
+        All free-text and scales filled with and set to locally saved answers
+        Submit button is enabled
+        """
+        # load JSON entries
+        self.load_json_entries()
+
+        # get existing entry
+        for entry in self.entries:
+            if self.feedback_name == entry['name']:
+                self.entry = entry
+
+        # set widgets values to locally saved answers
+        try:
+            for key, w in self.widgets_container.items():
+                w.value = self.entry['answer'][key]
+        except KeyError:
+            with self.output:
+                print('Something went wrong! Contact notebook provider.')
+
+    def ui_state_mail_sent(self):
+        """
+        Sets UI state to mail_sent
+
+        All widgets are filled with locally saved answers
+        All widgets and submit button are disabled
+        """
+        # call saved_locally state for filling widgets with saved answers
+        self.ui_state_saved_locally()
+
+        # disable all widgets
+        for w in self.widgets_container.values():
+            w.disabled = True
+
+        # disable button and change description
+        self.btn_submit.disabled = True
+        self.btn_submit.description = self.feedback_text['sent']