Skip to main content

Crate emailaddr

Crate emailaddr 

Source
Expand description

emailaddr

Type-safe, zero-copy, validated email addresses with generic storage, IDNA domain support, and no_std/no-alloc compatibility.

github LoC Build codecov

docs.rs crates.io crates.io license

Discord

§Overview

emailaddr provides validated email address types with a storage model similar to hostaddr:

  • EmailAddr<S = str> stores a validated full address using caller-chosen storage.
  • LocalPart<S = str> validates dot-atom and quoted-string local-parts.
  • DomainPart<S = str> validates RFC 5321 domain-parts and bracketed address literals.
  • Buffer stores a full validated address on the stack for no_std / no-allocation use.

With the default std feature, SMTPUTF8 local-parts are accepted and Unicode domain-parts are normalized to IDNA/punycode. The verify_ascii_* helpers remain strict ASCII validators.

§Installation

[dependencies]
emailaddr = "0.1"

§Quick Start

use emailaddr::EmailAddr;

let addr: EmailAddr<String> = "user.name@example.com".parse().unwrap();
assert_eq!(addr.local_part().as_inner(), &"user.name");
assert_eq!(addr.domain_part().as_inner(), &"example.com");

let quoted = EmailAddr::try_from_ascii_str("\"user name\"@example.com").unwrap();
assert_eq!(quoted.local_part().as_inner(), &"\"user name\"");

let idn: EmailAddr<String> = "user@测试.中国".parse().unwrap();
assert_eq!(idn.as_str(), "user@xn--0zwm56d.xn--fiqs8s");

let smtp_utf8: EmailAddr<String> = "用户@example.com".parse().unwrap();
assert_eq!(smtp_utf8.local_part().as_inner(), &"用户");

§Borrowed DSTs

use emailaddr::{DomainPart, EmailAddr, LocalPart};

let addr: &EmailAddr<str> = EmailAddr::try_from_ascii_str("user@example.com").unwrap();
assert_eq!(addr.as_inner(), "user@example.com");
assert_eq!(addr.local_part_ref().as_inner(), "user");
assert_eq!(addr.domain_part_ref().as_inner(), "example.com");

let bytes: &EmailAddr<[u8]> = addr.as_bytes_addr();
assert_eq!(bytes.as_str_addr().as_inner(), "user@example.com");

let local: &LocalPart<str> = LocalPart::try_from_ascii_str("user").unwrap();
let domain: &DomainPart<str> = DomainPart::try_from_ascii_str("example.com").unwrap();
assert_eq!(local.as_bytes().as_inner(), b"user");
assert_eq!(domain.as_bytes().as_inner(), b"example.com");

§Storage

use emailaddr::{Buffer, EmailAddr};
use std::sync::Arc;

let owned: EmailAddr<String> = "user@example.com".parse().unwrap();
let shared: EmailAddr<Arc<str>> = EmailAddr::try_from("user@example.com").unwrap();
let bytes: EmailAddr<Vec<u8>> = EmailAddr::try_from(b"user@example.com".as_slice()).unwrap();
let stack: EmailAddr<Buffer> = EmailAddr::try_from("user@example.com").unwrap();

assert_eq!(owned.as_str(), shared.as_str());
assert_eq!(bytes.as_bytes(), stack.as_bytes());

§Validation Helpers

use emailaddr::{
    verify_ascii_domain_part,
    verify_ascii_email_addr,
    verify_ascii_local_part,
    verify_email_addr,
};

assert!(verify_ascii_email_addr(b"user@example.com").is_ok());
assert!(verify_email_addr("user@测试.中国".as_bytes()).is_ok());
assert!(verify_email_addr("用户@example.com".as_bytes()).is_ok());
assert!(verify_ascii_local_part(b"user.name").is_ok());
assert!(verify_ascii_domain_part(b"[IPv6:::1]").is_ok());

assert!(verify_ascii_email_addr(b"user..name@example.com").is_err());
assert!(verify_ascii_email_addr(b"user@example_com").is_err());
assert!(verify_ascii_email_addr(b"user@example.com.").is_err());

§Parsing Options

Default parsing stays RFC-compatible. Options adds application policy and explicit non-standard relaxations without changing the default constructors. parse_with_options is available on EmailAddr<S, Relax>, so relaxed parsing is visible in the type while ordinary EmailAddr<S> remains strict.

use emailaddr::{
    Buffer,
    DomainOptions,
    DomainUnicodePolicy,
    EmailAddr,
    Limits,
    LocalOptions,
    Options,
    Relax,
    SmtpUtf8Policy,
};

let require_tld = Options::new()
    .with_domain(DomainOptions::new().with_required_tld());
assert!(EmailAddr::<Buffer, Relax>::parse_with_options("ted.backer@gmail", require_tld).is_err());

let long_local = Options::new()
    .with_limits(Limits::new().with_max_local_part_len(128));
assert!(EmailAddr::<Buffer, Relax>::parse_with_options(
    "reply+2a907e&3uofr1&&99cd5c22c2ca5b23655799316a8d8eb2dd83c3c487612cb9b9a00bf13f13afe2@mg1.substack.com",
    long_local,
).is_ok());

let ascii_local = Options::new()
    .with_local(LocalOptions::new().with_smtp_utf8(SmtpUtf8Policy::Forbid));
assert!(EmailAddr::<Buffer, Relax>::parse_with_options("用户@example.com", ascii_local).is_err());

let no_literals = Options::new()
    .with_domain(DomainOptions::new().without_domain_literals());
assert!(EmailAddr::<Buffer, Relax>::parse_with_options("user@[127.0.0.1]", no_literals).is_err());

let raw_domain = Options::new()
    .with_domain(DomainOptions::new().with_unicode(DomainUnicodePolicy::NonStandardUtf8));
let addr = EmailAddr::<Buffer, Relax>::parse_with_options("👋@💌.kz", raw_domain).unwrap();
assert_eq!(addr.as_str(), "👋@💌.kz");

DomainUnicodePolicy::NonStandardUtf8 preserves raw UTF-8 DNS labels as application data. It is not SMTP/DNS-compatible IDNA normalization. The DomainUnicodePolicy::as_str() values, CLI values, and human-readable serde values for this policy are ascii, idna, and raw. Human-readable serde formats such as JSON, YAML, and TOML serialize option enums with those same string values, so configuration files can use fields like:

[domain]
unicode = "raw"
literals = "forbid"
minimum_dns_labels = 2

§Feature Flags

FeatureDescription
std (default)Standard library support and IDNA domain normalization
allocAllocation support without std, including IDNA domain normalization
serdeserde_core-backed serialization/deserialization for addresses, parts, and parsing options; combine with alloc or std for IDNA-validated A-label deserialization
clapclap::Args / ValueEnum support for parsing options; CLI flags only, no environment-variable wiring
zeroizezeroize::Zeroize support for Buffer, EmailAddr, LocalPart, and DomainPart when the selected storage can be wiped
bytes / bytes_1bytes::Bytes storage support
smallvec / smallvec_1smallvec::SmallVec byte storage support
smol_str / smol_str_0_3smol_str::SmolStr storage support
tinyvec / tinyvec_1tinyvec::TinyVec byte storage support
triomphe / triomphe_0_1triomphe::Arc<str> and triomphe::Arc<[u8]> storage support
arbitraryInvariant-preserving arbitrary::Arbitrary impls for owned storage backends
quickcheckInvariant-preserving quickcheck::Arbitrary impls for owned storage backends

Calling zeroize clears the underlying storage and intentionally destroys the validated-address invariant. Drop or reassign a value after wiping it.

§RFC Coverage

emailaddr validates the address forms relevant to an address value:

  • RFC 5321 SMTP mailbox syntax and length limits
  • RFC 5322 addr-spec local-part/domain token grammar where it overlaps with mailbox addresses
  • RFC 6531 SMTPUTF8 local-parts
  • RFC 6532 UTF-8 header character model for address tokens
  • RFC 1123 host label digit relaxation for domains
  • RFC 4291 IPv6 address literals
  • RFC 5890 IDNA domain normalization
  • RFC 3629 UTF-8 validity for SMTPUTF8 input
  • RFC 5234 ABNF-derived parser behavior
  • RFC 3696 application-level email length guidance

This crate does not parse full message headers, display names, groups, comments, or SMTP commands; it validates and normalizes address values.

§License

emailaddr is under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE, LICENSE-MIT for details.

Copyright (c) 2026 Al Liu.

Structs§

Buffer
A stack-allocated buffer for a validated email address.
DomainOptions
Domain-part parsing options.
DomainPart
A validated email domain-part.
EmailAddr
A type-safe, validated email address.
Limits
Parsing limits.
LocalOptions
Local-part parsing options.
LocalPart
A validated email local-part.
Options
Email address parsing options.
ParseDomainPartError
The provided input is not a syntactically valid email domain-part.
ParseLocalPartError
The provided input is not a syntactically valid email local-part.
Relax
Relaxed email address policy for custom parsing options.
Strict
Strict email address policy.

Enums§

DomainLiteralPolicy
Policy for address-literal domain-parts.
DomainUnicodePolicy
Policy for Unicode domain input.
ParseEmailAddrError
The provided input is not a syntactically valid email address.
SmtpUtf8Policy
Policy for SMTPUTF8 local-parts.

Constants§

DEFAULT_MAX_LOCAL_PART_LENGTH
Default local-part length limit used by Limits.
DEFAULT_MINIMUM_DNS_LABELS
Default minimum DNS label count used by DomainOptions.
MAX_DOMAIN_PART_LENGTH
The maximum DNS domain length accepted for email domains.
MAX_EMAIL_ADDR_LENGTH
The maximum email address length accepted by this crate.
MAX_LOCAL_PART_LENGTH
The maximum local-part length, in bytes.

Traits§

EmailAddrSerdeStorageserde
Storage that can expose a validated email address, local-part, or domain-part as a UTF-8 string for serde serialization.

Functions§

is_atext
Returns true if byte is valid in an unquoted email atom.
verify_ascii_dns_domain
Verifies that input is a valid ASCII DNS domain name.
verify_ascii_domain_part
Verifies that input is a valid ASCII email domain-part.
verify_ascii_email_addr
Verifies that input is a valid ASCII email address.
verify_ascii_local_part
Verifies that input is a valid ASCII email local-part.
verify_email_addralloc or std
Verifies that input is a valid email address.
verify_email_addr_with_options
Verifies that input is a valid email address under custom parsing options.
verify_local_part
Verifies that input is a valid email local-part.

Type Aliases§

RelaxedParseOutputalloc or std
Output from relaxed parsing that may preserve the original storage or return a stack buffer.