AA Skip Email (aa_skip_email)
A small Alliance Auth plugin that lets you skip collecting real email addresses
during EVE SSO registration by automatically generating a placeholder User.email.
This is useful for integrations (e.g., OIDC clients) that expect email to be present.
Features
- Creates a placeholder email for newly created users during SSO registration.
- Includes a one-off Django management command to backfill missing emails.
- Includes a Celery task suitable for periodic execution to keep data consistent.
- Emits a
placeholder_email_replacedsignal when a user's placeholder is replaced with a real address — downstream plugins (OIDC providers, audit logs, welcome flows) can react without polling. - Exposes
is_placeholder_email()andget_placeholder_users()helpers so callers can classify addresses without re-implementing the domain check. - Ships a
PlaceholderEmailFilterfor the Django admin user list. - Translated user-visible strings (admin filter, command output,
AppConfig name) — currently
ensource +rucatalogue.
Requirements
- Python >= 3.12 (tested on 3.12 and 3.13)
- Alliance Auth >= 4.0, < 6 (tested against the latest AA 4.x and AA 5.x)
Installation
Install the plugin into the same Python environment as Alliance Auth (pip/uv/poetry-whatever you use).
pip install aa-skip-email
Add the app to INSTALLED_APPS (typically in local.py):
INSTALLED_APPS += [
...
"aa_skip_email",
...
]
Add the authentication backend to AUTHENTICATION_BACKENDS.
Example:
AUTHENTICATION_BACKENDS = [
# Replace the original AA StateBackend with this plugin backend
"aa_skip_email.authentication.backends.SkipEmailBackend",
# Keep the rest of your original backends
"django.contrib.auth.backends.ModelBackend",
]
Configuration
Configure these in local.py (or your equivalent settings file).
AA_SKIP_EMAIL_DOMAIN
Domain used for placeholder addresses.
- Type:
str - Default:
no-email.invalid
AA_SKIP_EMAIL_DOMAIN = "no-email.invalid"
Using a domain under .invalid helps avoid accidentally generating addresses at a real domain.
The plugin validates this setting at process start and raises
ImproperlyConfigured if the value is empty, contains @ or whitespace,
or has no . (single-label "domain"). This catches misconfigurations
before the first SSO registration attempt.
AA_SKIP_EMAIL_LIMIT
Default limit for how many users are processed per run by the management command and the Celery task.
- Type:
int - Default:
5000
AA_SKIP_EMAIL_LIMIT = 5000
How placeholder emails are generated
A placeholder email is generated from the user’s username (sanitized to a conservative character set) and a unique suffix (typically the user id; otherwise a UUID). The local-part is kept within 64 characters.
Resulting format is similar to:
<sanitized-username>-<id-or-uuid>@<AA_SKIP_EMAIL_DOMAIN>
One-off backfill (management command)
The plugin provides a Django management command to fill missing emails.
Fill missing emails (default)
python manage.py fill_missing_emails
Limit number of users
python manage.py fill_missing_emails --limit 5000
Dry-run (no writes)
python manage.py fill_missing_emails --dry-run --limit 50
Overwrite emails for all users
python manage.py fill_missing_emails --overwrite
Overwrite only existing placeholders
Safer if some users have real emails and you only want to regenerate placeholders.
python manage.py fill_missing_emails --overwrite --only-placeholders
Scheduler (Celery Beat)
The plugin includes a Celery task named:
aa_skip_email.fill_missing_emails
To run it periodically, add a schedule entry in local.py using CELERYBEAT_SCHEDULE.
Example: run every 6 hours
from celery.schedules import crontab
CELERYBEAT_SCHEDULE["aa_skip_email_fill_missing_emails"] = {
"task": "aa_skip_email.fill_missing_emails",
"schedule": crontab(minute=0, hour="*/6"),
# Optional: helps spread load across instances in some AA deployments
"apply_offset": True,
}
Example: run daily at 04:15
from celery.schedules import crontab
CELERYBEAT_SCHEDULE["aa_skip_email_fill_missing_emails"] = {
"task": "aa_skip_email.fill_missing_emails",
"schedule": crontab(minute=15, hour=4),
"apply_offset": True,
}
Public API for downstream plugins
is_placeholder_email(email)
from aa_skip_email import is_placeholder_email
if is_placeholder_email(user.email):
... # treat as unverified / non-routable
Centralised classifier — returns True for any address produced by
this plugin (case-insensitive domain match, anchored to the domain
part). Falsy values (None, "") return False.
get_placeholder_users()
from aa_skip_email.helpers import get_placeholder_users
count = get_placeholder_users().count()
ORM-side analogue of is_placeholder_email. Returns a queryset of
all users whose email is a plugin-generated placeholder. Useful
for admin reports, audit dashboards, or batch follow-up.
Admin filter
Add PlaceholderEmailFilter to AA's existing UserAdmin.list_filter
to surface a "Placeholder / Real / Blank" sidebar in the admin user
list. The plugin does not re-register the User model so AA's
custom UserAdmin remains intact:
# in your local.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from aa_skip_email.admin import PlaceholderEmailFilter
User = get_user_model()
UserAdmin = admin.site._registry[User].__class__
UserAdmin.list_filter = (*UserAdmin.list_filter, PlaceholderEmailFilter)
Signal placeholder_email_replaced
Sent when User.email transitions from a plugin-generated placeholder
to a real address. Useful for OIDC providers that flip
email_verified=true on the ID token, audit logs, welcome emails, etc.
from django.dispatch import receiver
from aa_skip_email.signals import placeholder_email_replaced
@receiver(placeholder_email_replaced, dispatch_uid="my_app.email_verified")
def on_email_replaced(sender, user, old_email, new_email, **kwargs):
...
The signal does not fire on:
- Initial fill of a blank/NULL email (placeholder is the new value).
- Placeholder rewritten as another placeholder (
--overwrite --only-placeholders). - Real email replaced with another real email.
See also
Metadata
Release files for aa-skip-email 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aa_skip_email-0.4.1.tar.gz | 16.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aa_skip_email-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.0 kB
Release files / aa_skip_email-0.4.1.tar.gz
| Download URL | aa_skip_email-0.4.1.tar.gz |
|---|---|
| Size | 16.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f929a5a35ff0474b9198c85e97e01f5962c57da98ad052a953d683da230f3668
|
|
BLAKE2b-256 checksum How to use checksums |
2d980f0f48a64e98a4125a825958b593814e43077dbebbc5b563fd06a94e2054
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / aa_skip_email-0.4.1-py3-none-any.whl
| Download URL | aa_skip_email-0.4.1-py3-none-any.whl |
|---|---|
| Size | 21.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b4b0ac61e4da8f0ba5dbdd43abe3c74ef0384a9faadc10ff69c551f64b1b31f2
|
|
BLAKE2b-256 checksum How to use checksums |
104f3105274dbd3eb3cd41816cbb08435fdafec544d2e59f2e60bc055e615517
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for aa-skip-email 0.4.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.