Skip to contents

Companion helper (ADR 0006) that reports structural, per-recipient facts about the recipients in the positional to of a mailto: URL — the comma-separated addr-spec list before the ? (RFC 6068 section 2). Recipients carried in to/cc/ bcc hfields are RFC 5322 address-lists and are deliberately out of scope; only the positional list is analysed.

Usage

get_mailto_recipients(
  url,
  url_standard = "rfc3986",
  scheme_policy = c("infer", "require"),
  scheme_acceptance = c("general", "web"),
  smtp_wire = FALSE
)

Arguments

url

A character vector of URLs. Non-mailto: URLs (and, under scheme_acceptance = "web", all mailto: URLs, which the web allowlist does not accept) contribute no rows.

url_standard

Standard profile passed to the general parser. A mailto: path is opaque, so this does not affect the classification; it defaults to "rfc3986" because the general parser requires a selector.

scheme_policy

Controls whether scheme-less, host-shaped input is accepted (an input-acceptance axis, distinct from protocol_handling, which only controls how the scheme is presented, and from url_standard, which controls interpretation). Defaults to "infer".

  • "infer": (Default) Fabricate http:// for scheme-less host-shaped input (e.g. example.com parses as http://example.com), a browser-omnibox-style affordance. This is the historical behavior.

  • "require": Reject scheme-less input — a scheme-less host-shaped value becomes parse_status = "error" rather than gaining a fabricated scheme. Use this for a strict, pure-parser posture. Note this governs only bare host input; scheme-relative //host input is governed separately by scheme_relative_handling.

scheme_acceptance

Which scheme tokens may enter parsing (a scheme-acceptance axis, distinct from scheme_policy, which governs scheme-less input, and from url_standard, which governs interpretation). Defaults to "web".

  • "web": (Default) Only the curated web-scheme allowlist (http/https/ftp/ftps/file) is admitted; a scheme-bearing input outside it is parse_status = "error". This is the historical, byte-for-byte compatible behavior.

  • "general": Admit any syntactically valid scheme token and parse opaque (mailto:x), non-special (foo://host), and RFC-generic URLs. Requires an explicit url_standard ("rfc3986" or "whatwg"), which decides the interpretation; general with url_standard = NULL is an error. Non-special / opaque hosts receive no www-stripping, no domain/TLD derivation, and are never run through the IDNA/punycode helpers. A non-special scheme with no // is an opaque path: it has no authority, so host, user, port and the domain/tld columns are all NA and the entire remainder is the path (query/fragment are still split off). This includes mailto: — the recipient's @ never re-triggers authority parsing. To decompose a mailto: recipient, use the accessors (get_host() / get_domain() / get_user(), ADR 0012 D7) or get_mailto_recipients(); those deliberately return a recipient's parts where this table presents NA, because a recipient domain is extraction metadata, not the URL's authority.

smtp_wire

Logical; when TRUE, compute the opt-in SMTP wire-projection columns (see the corresponding section). Defaults to FALSE.

Value

A data.frame (always, including for length-1 or all-empty input) with one row per positional-to recipient and columns:

url

the source URL the recipient came from.

recipient_index

1-based index of the recipient within that URL's positional list.

mailto_local_part_form

RFC 6068 local-part form: "dot-atom-text", "quoted-string", "invalid", or "indeterminate".

mailto_domain_form

RFC 6068 domain form: "ascii-dot-atom-text", "idna2008-domain", "bracketed-domain", "invalid", or "indeterminate".

smtp_mailbox_rhs_syntax_form

RFC 5321 mailbox RHS form, independent of the mailto grammar and of DNS: "domain", "address-literal", "invalid", or "indeterminate".

public_suffix_known

TRUE/FALSE whether the domain's public suffix is known to the PSL (non-validating); NA when the RHS is not a domain form.

smtp_domain_wire_form

(opt-in) "ascii-domain", "a-label-domain", "u-label-domain", "address-literal", or "unavailable".

smtp_envelope_wire_mode

(opt-in) "ascii", "smtputf8", or "unavailable".

smtp_envelope_address_requires_smtputf8

(opt-in) logical; NA when no wire projection could be made.

smtp_local_part_length_ok

(opt-in) logical, serialized local-part at most 64 octets; NA when unavailable.

smtp_direct_forward_path_fits

(opt-in) logical, the octet length of "<" + Mailbox + ">" is at most 256; NA when unavailable. The familiar 254 is the RFC 3696 EID 1690 derivation of this path limit, not a standalone production.

Details

Each fact names the grammar it was judged against. The left of the addr-spec is classified as an RFC 6068 local-part and, independently, the right is classified both as RFC 6068 mailto domain vocabulary and as an SMTP (RFC 5321) mailbox right-hand side — these are distinct grammars, so no single column spans them. Public-suffix knowledge is reported separately and is explicitly non-validating: a known suffix is not mailbox validity.

Facts, not a gate

These are selected structural facts, not a conformance oracle and never a validator. A recipient's classification never turns a parse into an error, and the absence of an invalid value does not imply the address is deliverable or fully RFC-conformant. No DNS resolution or deliverability check is performed.

SMTP wire-projection facts (opt-in)

Set smtp_wire = TRUE to additionally compute the SMTP transport facts that require an actual serialized wire projection of the address (octet-length limits per RFC 5321 section 4.5.3.1, and the SMTPUTF8 envelope mode per RFC 6531/6530). These are octet facts on the UTF-8 wire bytes, distinct from the syntax classifications above and from DNS. When smtp_wire = FALSE (the default) the five smtp_* wire columns are still present but carry the "unavailable"/NA sentinel; the same sentinel is used for a recipient whose address cannot be projected (an invalid mailbox).

Provenance-preserving parse

The positional list is tokenized on the raw (still percent-encoded) source before decoding, so an encoded comma (%2C) is never a recipient separator and an encoded quote or bracket (%22, %5B/%5D) still protects a raw comma; each field is then percent-decoded exactly once and classified.

Examples

get_mailto_recipients("mailto:jane@example.com",
  scheme_acceptance = "general")
#>                       url recipient_index mailto_local_part_form
#> 1 mailto:jane@example.com               1          dot-atom-text
#>    mailto_domain_form smtp_mailbox_rhs_syntax_form public_suffix_known
#> 1 ascii-dot-atom-text                       domain                TRUE
#>   smtp_domain_wire_form smtp_envelope_wire_mode
#> 1           unavailable             unavailable
#>   smtp_envelope_address_requires_smtputf8 smtp_local_part_length_ok
#> 1                                      NA                        NA
#>   smtp_direct_forward_path_fits
#> 1                            NA
get_mailto_recipients(
  "mailto:a@example.com,\"b,c\"@example.org",
  scheme_acceptance = "general"
)
#>                                      url recipient_index mailto_local_part_form
#> 1 mailto:a@example.com,"b,c"@example.org               1          dot-atom-text
#> 2 mailto:a@example.com,"b,c"@example.org               2          quoted-string
#>    mailto_domain_form smtp_mailbox_rhs_syntax_form public_suffix_known
#> 1 ascii-dot-atom-text                       domain                TRUE
#> 2 ascii-dot-atom-text                       domain                TRUE
#>   smtp_domain_wire_form smtp_envelope_wire_mode
#> 1           unavailable             unavailable
#> 2           unavailable             unavailable
#>   smtp_envelope_address_requires_smtputf8 smtp_local_part_length_ok
#> 1                                      NA                        NA
#> 2                                      NA                        NA
#>   smtp_direct_forward_path_fits
#> 1                            NA
#> 2                            NA
# opt-in SMTP wire-projection facts
get_mailto_recipients("mailto:a@xn--mnchen-3ya.de",
  scheme_acceptance = "general", smtp_wire = TRUE)
#>                          url recipient_index mailto_local_part_form
#> 1 mailto:a@xn--mnchen-3ya.de               1          dot-atom-text
#>    mailto_domain_form smtp_mailbox_rhs_syntax_form public_suffix_known
#> 1 ascii-dot-atom-text                       domain                TRUE
#>   smtp_domain_wire_form smtp_envelope_wire_mode
#> 1        a-label-domain                   ascii
#>   smtp_envelope_address_requires_smtputf8 smtp_local_part_length_ok
#> 1                                   FALSE                      TRUE
#>   smtp_direct_forward_path_fits
#> 1                          TRUE