python-email-validator: Rejecting `example.com` addresses

Starting in v1.2.0, email-validator is rejecting example.com addresses. While this is consistent with its documented behavior,

Other Special Use Domain Names are always considered invalid and raise EmailUndeliverableError.

it would seem to violate IETF RFC 6761 section 6.5 which says :

Application software SHOULD NOT recognize example names as special and SHOULD use example names as they would other domain names.

Sadly, when our CI pulled in the new release, our testing broke, because we use example.com in our test code.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 13
  • Comments: 16 (7 by maintainers)

Commits related to this issue

Most upvoted comments

I’m with @webbnh on this, but I’d go slightly further… I understand that this came with the best intentions, but you know how the saying goes 😃 I think this feature should be opt-in rather than opt-out in the first place, with a separate boolean flag controlling it ; this way, you give dependents time to implement their own wrappers around the new boolean flag (whether they want to support the extended “pick which domains are special” approach is probably up to them in the first place) - the reason I’m wording it that way is because (and I suppose this is common) I don’t actually directly use python-email-validator but flask-wtforms, which uses wtforms, which can optionally work with python-email-validator (notice the three layers deep, relatively obscure breakage from my POV, hehe).

I’ve pushed a commit that removes example and example.com/net/org from the special use domains list. I’ll make a release later in the week.

RFC 6761 says applications “SHOULD NOT” treat the “example” domains as special, which doesn’t mean it’s wrong to reject them (it’s not MUST NOT), but I get the problem. Since these domain names are reserved for IANA, I think it’s safe for this library to accept them. (Since they are reserved for documentation, it’s probably not appropriate to use these addresses generally in tests.)

In the latest release of this library, DNS-based deliverability checks will fail these domains anyway because example.com/net/org publish a NULL MX record (that’s new in this release) and the subdomains (e.g. mail.example) don’t resolve (that behavior is old). So beware that even with these domains removed from the special-use list, they may still fail. But with check_deliverability=False (which is probably a good idea in test environments anyway), they will now pass. (“example” alone, i.e. “josh@example”, doesn’t have a period which this library has always failed as a syntax error.)

The RFC says the same thing about “test,” but because “test” is not reserved to IANA and the RFC warns that users should be aware that “test” may resolve differently in different environments, this domain does not seem to me to be safe to pass validation.

The latest release of this library has a new keyword argument test_environment. When it is true, DNS-based deliverabiltiy checks are disabled (same as check_deliverability=False) and “test” is removed from the special-use domains list so that it can be used in tests (e.g. “me@test” or “me@mail.test” etc.). I think giving test_environment=True and using @test email addresses is probably the right approach for creating tests, rather than using @example.com/net/org addresses. But with the latest commit, using example domains would also work.

How does this all sound?

Thank you so much for the quick fix Joshua. All tests are passing again!

I’ve pushed an updated package to pypi and GitHub releases. In 1.2.1:

  • example and example.com/net/org are removed from the special-use domains list.
  • SPECIAL_USE_DOMAIN_NAMES is now a documented part of the API (and it is a list instead of a tuple)
  • New module-level attributes ALLOW_SMTPUTF8, CHECK_DELIVERABILITY, TEST_ENVIRONMENT, and DEFAULT_TIMEOUT can be used to change the default values of the keyword arguments.
  • Travis CI is updated to test from Python 3.6 forward, dropping 3.5.

@JoshData, that’s definitely a plausible approach.

However, to me, that feels like reaching inside what should be an opaque object and messing with its guts, but that’s probably my own problem – I come from a mindset where a package interface should be functional, and package data should be readonly, immutable, or hidden. Now, I know that that’s not really feasible in Python (and I note that SPECIAL_USE_DOMAIN_NAMES does not begin with an underscore…but I don’t consider that lack as positive permission to modify it in production code!)

So, if you want to document SPECIAL_USE_DOMAIN_NAMES as a mutable and part of the package interface, that’s fine…but I personally would prefer an API function for getting and/or setting it. But, as I say, that’s just me… 😉

Ah! Ok. I’ll look over the RFC and fix it (but I can’t do it immediately, I would recommend pinning to the prior version of the library for now).