Monoalphabetic Substitution Cipher
"abcdefghijklmnopqrstuvwxyz" is just the constant string.ascii_lowercase.

Whenever you have this pattern:

some_list = []
for dummy in some_iterable:
    some_list.append(some_function_of(dummy))


… that's a candidate for replacement with a list comprehension.

alpha.index(letter) and its inverse could be a performance issue when processing longer messages.  Making a dictionary for the translation table would be better.  Note that any character that is not ASCII lowercase will cause a ValueError.  For encrypt(),

translation = dict(zip(key, string.ascii_lowercase))
ciphertext = ''.join(translation.get(c, c) for c in original)


Here, translation.get(c, c) causes unrecognized characters to be passed through.  You can use translation[c] to restore the original exception-raising behaviour.