EDIT: This will work as you described:
try:
    msg = make_msg_fancy(msg)
    msg = check_for_spam(msg)
except MessageNotFancyException:
    print("couldn't make it fancy :(")
except MessageFullOfSpamException:
    print("too much spam :(")
When an exception occurs, it skips the rest of the try block and continues at the exception... it doesn't go back.
You are doing something like this:
for person in [{"dog": "Henry"}, {}, {"dog": None}]:
    try:
        doggo = person['dog']  # can throw KeyError
    except KeyError:
        print("Not a dog person")
        continue  # skip the rest of the loop (since this is a for loop)
    try:
        print(doggo.upper())  # can throw AttributeError
    except AttributeError:
        print("No doggo :(")
A better way is, as Christian suggested:
for person in [{"dog": "Henry"}, {}, {"dog": None}]:
    try:
        doggo = person['dog']  # can throw KeyError
        print(doggo.upper())  # can throw AttributeError
    except KeyError:  # person dict does not contain a "dog"
        print("Not a dog person")
    except AttributeError:  # dog entry cannot be .upper()'d
        print("invalid doggo :(")
Both of which output:
HENRY
Not a dog person
invalid doggo :(
Note this will skip the second set of lines automatically if the first set fails, and lets you do different things based upon which exception occurred.
I think you're confused. After a KeyError above, execution continues after the except blocks. The rest of the try: is skipped, which is what you seem to want:
That's why I can do:
try:
    dct[key] += value
    print("Added.")
except KeyError:
    dct[key] = value
    print("New key.")
Only one of those prints will happen.