It looks like each if/else branch has the same structure.   I think this would be clearer if you used a constant `dict` to associate message fields with templates.  Something like:

    EMAIL_TEMPLATE_DICT = {'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'), ...}

Then you could simplify the function to something like:

    subject_template, message_template = EMAIL_TEMPLATE_DICT.get(d['message'], (None, None))
    if subject_template is not None and message_template is not None:
        subject = render_to_string(subject_template, d)
        message = render_to_string(message_template, d)

You'd have to also explicitly handle (and test) when an invalid message type gets passed in.  This is something that's worth checking -- right now you're the only one calling `send_mail_to_student`, but in the future other people might use the function and pass in something unexpected for `d['message']`.