diff --git a/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt b/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt
index 1bfa541..730c4b6 100644
--- a/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt
+++ b/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt
@@ -8,18 +8,19 @@ private const val SMTP_SUBMISSION_PORT = 587
private const val SMTPS_PORT = 465
/**
- * Preconfigured IMAP/SMTP presets for the app-password vendors (Gmail, Yahoo, iCloud).
+ * Preconfigured IMAP/SMTP presets for the app-password vendors (Gmail, Yahoo, iCloud, AOL).
*
* These mirror [Account.outlook]: each entry knows its servers so onboarding only has to collect an
* email + app password (see the app-password setup screen). Outlook is intentionally NOT here — it
* uses interactive OAuth, not an app password.
*
* Port / security rationale (verified against current vendor docs):
- * - IMAP is implicit TLS on 993 for all three: none of these vendors document a STARTTLS IMAP
+ * - IMAP is implicit TLS on 993 for all four: none of these vendors document a STARTTLS IMAP
* endpoint, so 993/[MailSecurity.SSL_TLS] is the only correct choice.
* - SMTP biases toward STARTTLS on 587 where the vendor documents it (Gmail, iCloud), matching the
- * epic's "prefer STARTTLS where supported" guidance. Yahoo documents implicit TLS on 465 as its
- * outgoing server, so it keeps 465/[MailSecurity.SSL_TLS].
+ * epic's "prefer STARTTLS where supported" guidance. Yahoo and AOL each document only implicit
+ * TLS on 465 for their outgoing server (no STARTTLS/587 alternative is documented), so both keep
+ * 465/[MailSecurity.SSL_TLS].
* - [MailSecurity.NONE] is never used — every path here is encrypted end to end.
*/
enum class MailProvider(
@@ -32,7 +33,7 @@ enum class MailProvider(
/**
* Setup instructions for the provider's two-factor prerequisite, or null when there isn't one.
* Gmail and iCloud both refuse to issue app passwords until two-factor auth is on, so both
- * link their own setup article; Yahoo gates nothing on it.
+ * link their own setup article; Yahoo and AOL gate nothing on it.
*/
val twoFactorHelpUrl: String? = null,
private val imapHost: String,
@@ -91,6 +92,20 @@ enum class MailProvider(
smtpPort = SMTP_SUBMISSION_PORT,
smtpSecurity = MailSecurity.STARTTLS,
),
+ AOL(
+ key = "aol",
+ displayName = "AOL Mail",
+ // AOL's own "Create and manage 3rd-party app passwords" article. Unlike Gmail/iCloud, it
+ // never lists two-step verification as a prerequisite for generating an app password (nor
+ // does AOL's separate two-step-verification article call itself one), so there is no
+ // twoFactorHelpUrl below — same as Yahoo (issue #156).
+ appPasswordHelpUrl = "https://help.aol.com/articles/Create-and-manage-app-password",
+ imapHost = "imap.aol.com",
+ smtpHost = "smtp.aol.com",
+ // AOL documents smtp.aol.com:465 with implicit SSL/TLS as its outgoing server (issue #154).
+ smtpPort = SMTPS_PORT,
+ smtpSecurity = MailSecurity.SSL_TLS,
+ ),
;
/**
diff --git a/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt b/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt
index 2482252..6fc780d 100644
--- a/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt
+++ b/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt
@@ -268,16 +268,18 @@ private fun providerIntro(provider: MailProvider): Int = when (provider) {
MailProvider.GMAIL -> R.string.app_password_intro_gmail
MailProvider.YAHOO -> R.string.app_password_intro_yahoo
MailProvider.ICLOUD -> R.string.app_password_intro_icloud
+ MailProvider.AOL -> R.string.app_password_intro_aol
}
/**
* Button copy for the two-factor prerequisite link, in each provider's own terminology — Google
* calls it "2-Step Verification", Apple "Two-Factor Authentication". Only reached for providers
- * that expose [MailProvider.twoFactorHelpUrl]; Yahoo has none, so its branch here is unused.
+ * that expose [MailProvider.twoFactorHelpUrl]; Yahoo and AOL have none, so their branches here are
+ * unused.
*/
private fun twoFactorHelpLabel(provider: MailProvider): Int = when (provider) {
MailProvider.ICLOUD -> R.string.app_password_2fa_help_icloud
- MailProvider.GMAIL, MailProvider.YAHOO -> R.string.app_password_2fa_help
+ MailProvider.GMAIL, MailProvider.YAHOO, MailProvider.AOL -> R.string.app_password_2fa_help
}
private fun MailSecurity.label(): String = when (this) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0373255..8134527 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -189,12 +189,13 @@
Other (IMAP/SMTP)
Choose your email provider to get started.
-
+
Connect %1$s
Unknown email provider.
To connect Gmail, create an app password in your Google Account. Gmail requires 2-Step Verification to be turned on before you can create one.
To connect Yahoo Mail, generate an app password from your Yahoo Account security settings.
To connect iCloud Mail, create an app-specific password from your Apple ID account page.
+ To connect AOL Mail, generate an app password from your AOL Account security settings.
An app password is a one-off password that lets an app sign in to your account without your main password or a two-factor code.
Store this app password carefully — it grants full access to your email. LibreMail keeps it only on this device.
Create an app password for %1$s
diff --git a/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt b/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt
index 49f0ea1..6adc0e6 100644
--- a/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt
+++ b/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt
@@ -54,6 +54,19 @@ class MailProviderTest {
assertEquals(MailSecurity.STARTTLS, account.smtp.security)
}
+ @Test
+ fun `aol preset uses documented imap and implicit-tls smtp endpoints`() {
+ val account = MailProvider.AOL.createAccount("user@aol.com")
+
+ assertEquals("imap.aol.com", account.imap.host)
+ assertEquals(993, account.imap.port)
+ assertEquals(MailSecurity.SSL_TLS, account.imap.security)
+
+ assertEquals("smtp.aol.com", account.smtp.host)
+ assertEquals(465, account.smtp.port)
+ assertEquals(MailSecurity.SSL_TLS, account.smtp.security)
+ }
+
@Test
fun `no provider ever uses insecure transport`() {
MailProvider.entries.forEach { provider ->
@@ -82,7 +95,7 @@ class MailProviderTest {
}
@Test
- fun `gmail and icloud link two-factor setup help over https, yahoo does not`() {
+ fun `gmail and icloud link two-factor setup help over https, yahoo and aol do not`() {
// Gmail's app-passwords page rejects accounts without 2-Step Verification, so its setup
// screen must offer the setup article as a way out (issue #98).
val gmailUrl = assertNotNull(
@@ -100,6 +113,11 @@ class MailProviderTest {
// which never list two-step verification as a prerequisite for generating an app password
// (issue #155) — so it must not grow the extra link.
assertNull(MailProvider.YAHOO.twoFactorHelpUrl)
+
+ // AOL gates nothing on two-factor either — its app-password article never lists two-step
+ // verification as a prerequisite, and its separate two-step-verification article doesn't
+ // call itself one either (issue #156) — so it must not grow the extra link.
+ assertNull(MailProvider.AOL.twoFactorHelpUrl)
}
@Test
@@ -115,6 +133,14 @@ class MailProviderTest {
)
}
+ @Test
+ fun `aol app-password help points at AOL's dedicated app-password article`() {
+ assertEquals(
+ "https://help.aol.com/articles/Create-and-manage-app-password",
+ MailProvider.AOL.appPasswordHelpUrl,
+ )
+ }
+
@Test
fun `createAccount trims the email and derives a stable id and display name`() {
val account = MailProvider.GMAIL.createAccount(" User@Gmail.com ")
@@ -136,6 +162,7 @@ class MailProviderTest {
assertEquals(MailProvider.GMAIL, MailProvider.fromKey("gmail"))
assertEquals(MailProvider.YAHOO, MailProvider.fromKey("YAHOO"))
assertEquals(MailProvider.ICLOUD, MailProvider.fromKey("iCloud"))
+ assertEquals(MailProvider.AOL, MailProvider.fromKey("AOL"))
assertNull(MailProvider.fromKey("outlook"))
assertNull(MailProvider.fromKey(""))
}
@@ -163,6 +190,9 @@ class MailProviderTest {
assertEquals(MailProvider.GMAIL, MailProvider.forImapHost("imap.googlemail.com"))
assertEquals(MailProvider.GMAIL, MailProvider.forImapHost("IMAP.GMAIL.COM"))
assertEquals(MailProvider.ICLOUD, MailProvider.forImapHost("imap.mail.me.com"))
+ // A manually configured AOL account (set up before this preset existed) must still resolve
+ // to the AOL brand (issue #156).
+ assertEquals(MailProvider.AOL, MailProvider.forImapHost("imap.aol.com"))
assertNull(MailProvider.forImapHost("imap.example.org"))
}
@@ -176,6 +206,7 @@ class MailProviderTest {
fun `brandFor centralizes host to brand matching, including Outlook`() {
assertEquals("Gmail", MailProvider.brandFor(account("imap.googlemail.com")))
assertEquals("iCloud Mail", MailProvider.brandFor(account("imap.mail.me.com")))
+ assertEquals("AOL Mail", MailProvider.brandFor(account("imap.aol.com")))
// A manually configured Office 365 host is Outlook even without the OAuth auth type.
assertEquals(MailProvider.OUTLOOK_BRAND, MailProvider.brandFor(account("outlook.office365.com")))
// The OAuth auth type brands as Outlook regardless of host.