bounce.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. # -*- test-case-name: twisted.mail.test.test_bounce -*-
  2. #
  3. # Copyright (c) Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5. """
  6. Support for bounce message generation.
  7. """
  8. import StringIO
  9. import email.utils
  10. import time
  11. import os
  12. from twisted.mail import smtp
  13. BOUNCE_FORMAT = """\
  14. From: postmaster@%(failedDomain)s
  15. To: %(failedFrom)s
  16. Subject: Returned Mail: see transcript for details
  17. Message-ID: %(messageID)s
  18. Content-Type: multipart/report; report-type=delivery-status;
  19. boundary="%(boundary)s"
  20. --%(boundary)s
  21. %(transcript)s
  22. --%(boundary)s
  23. Content-Type: message/delivery-status
  24. Arrival-Date: %(ctime)s
  25. Final-Recipient: RFC822; %(failedTo)s
  26. """
  27. def generateBounce(message, failedFrom, failedTo, transcript=''):
  28. """
  29. Generate a bounce message for an undeliverable email message.
  30. @type message: L{bytes}
  31. @param message: The undeliverable message.
  32. @type failedFrom: L{bytes}
  33. @param failedFrom: The originator of the undeliverable message.
  34. @type failedTo: L{bytes}
  35. @param failedTo: The destination of the undeliverable message.
  36. @type transcript: L{bytes}
  37. @param transcript: An error message to include in the bounce message.
  38. @rtype: 3-L{tuple} of (E{1}) L{bytes}, (E{2}) L{bytes}, (E{3}) L{bytes}
  39. @return: The originator, the destination and the contents of the bounce
  40. message. The destination of the bounce message is the originator of
  41. the undeliverable message.
  42. """
  43. if not transcript:
  44. transcript = '''\
  45. I'm sorry, the following address has permanent errors: %(failedTo)s.
  46. I've given up, and I will not retry the message again.
  47. ''' % {'failedTo': failedTo}
  48. failedAddress = email.utils.parseaddr(failedTo)[1]
  49. data = {
  50. 'boundary': "%s_%s_%s" % (time.time(), os.getpid(), 'XXXXX'),
  51. 'ctime': time.ctime(time.time()),
  52. 'failedAddress': failedAddress,
  53. 'failedDomain': failedAddress.split('@', 1)[1],
  54. 'failedFrom': failedFrom,
  55. 'failedTo': failedTo,
  56. 'messageID': smtp.messageid(uniq='bounce'),
  57. 'message': message,
  58. 'transcript': transcript,
  59. }
  60. fp = StringIO.StringIO()
  61. fp.write(BOUNCE_FORMAT % data)
  62. orig = message.tell()
  63. message.seek(2, 0)
  64. sz = message.tell()
  65. message.seek(0, orig)
  66. if sz > 10000:
  67. while 1:
  68. line = message.readline()
  69. if len(line)<=1:
  70. break
  71. fp.write(line)
  72. else:
  73. fp.write(message.read())
  74. return '', failedFrom, fp.getvalue()