Nobody has started this yet — be first.
Business impact
The single most common thing a feedback or contact form asks for -- after the free-text comment itself -- is the respondent's email address, so the owner can follow up. Right now, an owner who wants that has to use type: "text", which accepts literally anything: "n/a", "call me", a phone number, a typo'd address that will bounce. Every one of those degrades the owner's contact list and wastes a follow-up email or outreach attempt on an address that was never going to work. This is a gap in one of the most common real-world use cases for a "feedback form" product, and it is a gap this codebase is specifically well-positioned to close cleanly, since its whole design point is a table-driven set of field types -- adding one more type is meant to be a natural, contained extension, not a rearchitecture.
Problem
FIELD_TYPES in app/models.py is ("text", "number", "choice", "multichoice", "file") -- there is no "email" type. Creating a form with a field of "type": "email" is rejected at form-creation time with "'type' must be one of [...]", before validation of any actual submitted answers ever comes into play. This project already depends on email_validator (see requirements.txt) and already uses it correctly in one place -- app/blueprints/auth/routes.py::register, to validate an owner's email at signup -- that is the tool to reach for here too, not a hand-rolled regex.
Current behavior
POST /api/forms with a field of "type": "email" returns 422 ("'type' must be one of [...]") instead of 201; there is currently no way to declare or validate an email-typed field at all.
Expected behavior
A form can declare a field with "type": "email". A submitted answer for that field must be a syntactically valid email address (per email_validator.validate_email) to be accepted; anything else is rejected with a 422 and a field_errors entry keyed by that field's name -- the same convention as every other field type in validate_submission_answers. Required/optional and empty-value handling for an email field follows the same rules as every other non-file field type.
Steps to reproduce
cd flask/feedback_service export FLASK_ENV=testing FLASK_ENV=testing .venv/bin/python -m pytest ticket_tests/test_ticket_05_email_field_type.py -v
Why this matters
This is the project's core architectural extension point, exercised for real: validate_submission_answers is a fixed, table-driven dispatch over field["type"] -- the README is explicit that this function must never eval/exec/dynamically import anything derived from form data, so a new field type has to be a new, ordinary elif branch, following the exact same fail-closed shape (append to errors[name] and continue on any problem; only add to cleaned[name] once a value is fully valid) as every branch already there. Getting this right is a good test of whether you have actually understood that pattern, versus just copy-pasting the text branch and calling it done.
Suggested approach
Start at app/models.py::FIELD_TYPES -- a field type has to be recognized there before validate_field_definitions will let a form declare it at all. Then look at validate_submission_answers in app/blueprints/forms/validators.py: find where the if ftype == "text": ... elif ftype == "number": ... chain lives, and add a branch for "email" that calls email_validator.validate_email(...), catching the specific exception it raises for an invalid address (see app/blueprints/auth/routes.py::register for exactly how that is already done once in this codebase) and turning that into a field error rather than letting it propagate.
Acceptance criteria
Verification
cd flask/feedback_service && FLASK_ENV=testing .venv/bin/python -m pytest ticket_tests/test_ticket_05_email_field_type.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
$git checkout -b feat/email-field-typeFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/email-field-typeSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inweb and worker share the same image and only differ in their command: -- web runs flask db upgrade && flask run, worker runs celery -A celery_worker.celery worker. For a prod-style stack (built image, gunicorn, no bind mount) use docker compose -f docker-compose.prod.yml up --build instead; it refuses to start without SECRET_KEY/JWT_SECRET_KEY actually set in the environment.
cd flask/feedback_service
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # defaults are fine for local dev as-is
export FLASK_APP=wsgi.py FLASK_ENV=development
flask db upgrade # applies migrations to a fresh SQLite DB under instance/
# terminal 1
flask run --port 5000
# terminal 2 (needs a local redis-server running — broker for both Celery and the rate limiter)
celery -A celery_worker.celery worker --loglevel=info
Each ticket in ticket_tests/tickets/0N_*.md names one dedicated test file under ticket_tests/:
cd flask/feedback_service
export FLASK_ENV=testing
.venv/bin/python -m pytest ticket_tests/test_ticket_0N_*.py -v # a single ticket
./ticket_tests/run_tickets.sh # all 7, PASS/FAIL scoreboard
TestingConfig runs Celery tasks eagerly (CELERY_TASK_ALWAYS_EAGER=True, no broker needed) and uses a real, per-test-flushed Redis DB for the rate limiter when Redis is reachable, falling back to Flask-Limiter's in-memory backend otherwise -- either way the real Flask-Limiter code path runs, nothing about rate limiting is mocked. Note ticket_tests/ is not collected by a plain pytest -q from the project root (pytest.ini pins testpaths = tests), so the project's own suite and this practice suite stay independent and must be run explicitly.
Level 2
Implement a feature
Extend the system within its own patterns.