Phase (b) of docs/DESIGN.md section 3.5–3.6. Stacked on #6 (independent of #7).
PostmarkPOST /email (server token, message stream, open/link tracking), SendGridPOST /v3/mail/send (personalizations, custom_args, tracking settings, X-Message-Id), MailgunPOST /v3/{domain}/messages.mime (US/EU, multipart built in memory, Bcc stripped from the MIME and passed as recipients).
One EmailHttp seam over java.net.http (10 s connect, 30 s request, HTTPS only, no redirects).
Error mapping: per-message rejections permanent; 429/5xx transient with Retry-After; bad or refused credentials and account-level errors provider-level (provider goes unhealthy, messages wait). Response bodies are never stored; fixed messages plus the provider's code.
Gmail:GMAIL_SMTP preset (smtp.gmail.com:587 STARTTLS, account and app-password env names required); GMAIL_API refused with a pointer to the preset, as specified.
docs/PROVIDERS.md: setup per provider, env names, limits, Gmail app passwords and daily limits.
Tests: 113 with #6, all green (request shapes, id parsing, every error class, secrets absent from errors, an end-to-end send per provider against a local HTTP server, Gmail validation). JaCoCo 94.5% instructions / 100% classes; Checkstyle clean.
Phase (b) of `docs/DESIGN.md` section 3.5–3.6. Stacked on #6 (independent of #7).
- **Postmark** `POST /email` (server token, message stream, open/link tracking), **SendGrid** `POST /v3/mail/send` (personalizations, custom_args, tracking settings, `X-Message-Id`), **Mailgun** `POST /v3/{domain}/messages.mime` (US/EU, multipart built in memory, Bcc stripped from the MIME and passed as recipients).
- One `EmailHttp` seam over `java.net.http` (10 s connect, 30 s request, HTTPS only, no redirects).
- Error mapping: per-message rejections permanent; 429/5xx transient with Retry-After; bad or refused credentials and account-level errors provider-level (provider goes unhealthy, messages wait). Response bodies are never stored; fixed messages plus the provider's code.
- **Gmail:** `GMAIL_SMTP` preset (smtp.gmail.com:587 STARTTLS, account and app-password env names required); `GMAIL_API` refused with a pointer to the preset, as specified.
- Per-kind validation and default env names (`POSTMARK_SERVER_TOKEN`, `SENDGRID_API_KEY`, `MAILGUN_API_KEY`, `GMAIL_USERNAME`, `GMAIL_APP_PASSWORD`, webhook secrets).
- `docs/PROVIDERS.md`: setup per provider, env names, limits, Gmail app passwords and daily limits.
Tests: 113 with #6, all green (request shapes, id parsing, every error class, secrets absent from errors, an end-to-end send per provider against a local HTTP server, Gmail validation). JaCoCo 94.5% instructions / 100% classes; Checkstyle clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase (b) of
docs/DESIGN.mdsection 3.5–3.6. Stacked on #6 (independent of #7).POST /email(server token, message stream, open/link tracking), SendGridPOST /v3/mail/send(personalizations, custom_args, tracking settings,X-Message-Id), MailgunPOST /v3/{domain}/messages.mime(US/EU, multipart built in memory, Bcc stripped from the MIME and passed as recipients).EmailHttpseam overjava.net.http(10 s connect, 30 s request, HTTPS only, no redirects).GMAIL_SMTPpreset (smtp.gmail.com:587 STARTTLS, account and app-password env names required);GMAIL_APIrefused with a pointer to the preset, as specified.POSTMARK_SERVER_TOKEN,SENDGRID_API_KEY,MAILGUN_API_KEY,GMAIL_USERNAME,GMAIL_APP_PASSWORD, webhook secrets).docs/PROVIDERS.md: setup per provider, env names, limits, Gmail app passwords and daily limits.Tests: 113 with #6, all green (request shapes, id parsing, every error class, secrets absent from errors, an end-to-end send per provider against a local HTTP server, Gmail validation). JaCoCo 94.5% instructions / 100% classes; Checkstyle clean.
🤖 Generated with Claude Code