Android (Kotlin)
dev.frontmail:frontmail umožní aplikaci pro Android (nebo jakékoli aplikaci v Kotlinu na JVM)
odesílat e-maily přes vaše šablony s public key – bez vlastního backendu. Je to obyčejná knihovna
v Kotlinu se suspend API; jediné závislosti jsou kotlinx-coroutines-core
a kotlinx-serialization-json.
Instalace
Sekce “Instalace”dependencies { implementation("dev.frontmail:frontmail:0.1.0")}Aplikace potřebuje Kotlin 2.2 nebo novější a oprávnění INTERNET:
<uses-permission android:name="android.permission.INTERNET" />Zdrojový kód a vydání: github.com/frontmail-dev/frontmail-kotlin.
Povolte mobilní aplikace
Sekce “Povolte mobilní aplikace”Nativní aplikace neposílají hlavičku Origin ani Referer, takže požadavky z aplikace (bez ohledu
na to, jestli máte nastavené povolené weby) skončí chybou
403 origin_not_allowed, dokud v dashboardu nezapnete
Zabezpečení → Mobilní aplikace → Povolit mobilní aplikace a nekliknete na Uložit. Viz
Nastavení zabezpečení.
Požadavky z webů se dál kontrolují podle seznamu, takže přepínač ochranu vašich webových formulářů neoslabí. Přepínač je potřeba i s prázdným seznamem.
Odeslání
Sekce “Odeslání”Vytvořte jednoho klienta a používejte ho opakovaně (je bezpečný pro více vláken). send je
suspend funkce, takže ji volejte z korutiny, např. z lifecycleScope nebo viewModelScope:
import dev.frontmail.AcceptedStatusimport dev.frontmail.Frontmailimport dev.frontmail.FrontmailException
val frontmail = Frontmail("pk_4f2a…")
viewModelScope.launch { try { val result = frontmail.send( templateId = "tpl_contact", params = mapOf("email" to email, "message" to message), serviceId = "svc_01J9…", // nepovinné ) state.value = if (result.status == AcceptedStatus.HELD) "Přijato – doručíme co nejdřív." else "Děkujeme!" } catch (e: FrontmailException) { state.value = e.message }}params přijímá řetězce, čísla, booleany, null, vnořené mapy a seznamy, pole a JsonElement;
existuje i varianta, která bere JsonObject. Bez serviceId se použije služba šablony.
result.status je QUEUED (přijato k doručení) nebo HELD (přijato, čeká na kredity).
result.statusToken slouží k pozdějšímu zjištění stavu doručení.
Volby klienta:
| Volba | Výchozí hodnota | Význam |
|---|---|---|
apiUrl |
https://api.frontmail.dev |
základní URL API |
retry |
RetryPolicy() (3 opakování) |
RetryPolicy(retries, baseDelayMillis, maxDelayMillis), RetryPolicy.NONE opakování vypne |
timeoutMillis |
15000 |
časový limit jednoho pokusu |
Private key (sk_…) vyhodí FrontmailException s kódem private_key_in_browser už v konstruktoru
– v aplikaci smí být jen public key.
Každý požadavek nese hlavičku X-Frontmail-Client: frontmail-kotlin/<verze>.
Přílohy
Sekce “Přílohy”Pokud šablona povoluje dynamické přílohy, předejte je v SendOptions:
frontmail.send( "tpl_job_application", mapOf("name" to name), options = SendOptions( attachments = listOf( Attachment.fromBytes("cv.pdf", "application/pdf", bytes), Attachment.Upload("upl_…"), // nahráno předem přes POST /v1/uploads ), ),)Turnstile
Sekce “Turnstile”Pokud šablona vyžaduje Turnstile, zobrazte widget Cloudflaru ve WebView
a token předejte do send. SDK nemá žádnou závislost na Androidu, takže vám dá jednotlivé díly
a WebView si zapojíte sami:
Turnstile.sharedWidget()– sdílený mobilní klíč Frontmailu. Site key nepotřebujete: SDK ho načte zGET /v1/public-config(s cache, takže jen jednou) a widget poběží jako vložená HTML stránka s adresouhttps://mobile.frontmail.dev– z té adresy se nic nestahuje.widget.html()– stránka s widgetem. Zprávy posílá do JavaScriptového mostuFrontmailTurnstile(Turnstile.ANDROID_BRIDGE_NAME).Turnstile.parseMessage(data)– přijme jen přesně ty zprávy, které stránka posílá; vrátíToken,Expired,Errornebonull.widget.navigationDecision(url, isForMainFrame)– hlídání navigace:ALLOW,OPEN_EXTERNALLY(odkazy ve widgetu) neboBLOCK.
@SuppressLint("SetJavaScriptEnabled")fun WebView.showTurnstile(widget: TurnstileWidget, onToken: (String) -> Unit, onExpire: () -> Unit) { settings.javaScriptEnabled = true settings.domStorageEnabled = true settings.allowFileAccess = false settings.allowContentAccess = false settings.javaScriptCanOpenWindowsAutomatically = false settings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW setBackgroundColor(Color.TRANSPARENT)
val main = Handler(Looper.getMainLooper()) addJavascriptInterface( object { @JavascriptInterface fun postMessage(data: String?) { main.post { when (val m = Turnstile.parseMessage(data)) { is TurnstileMessage.Token -> onToken(m.token) TurnstileMessage.Expired -> onExpire() is TurnstileMessage.Error -> Log.w("Turnstile", "error ${m.code}") null -> Unit } } } }, Turnstile.ANDROID_BRIDGE_NAME, ) webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean = when (widget.navigationDecision(request.url.toString(), request.isForMainFrame)) { Turnstile.NavigationDecision.ALLOW -> false Turnstile.NavigationDecision.OPEN_EXTERNALLY -> { runCatching { context.startActivity(Intent(Intent.ACTION_VIEW, request.url)) } true } Turnstile.NavigationDecision.BLOCK -> true } } loadDataWithBaseURL(widget.baseUrl, widget.html(), "text/html", "utf-8", null)}Token pak pošlete spolu s turnstileKey widgetu:
val widget = Turnstile.sharedWidget()webView.showTurnstile(widget, onToken = { token = it }, onExpire = { token = null })
// při odeslání:try { frontmail.send( "tpl_contact", mapOf("email" to email, "message" to message), options = SendOptions(turnstileToken = token, turnstileKey = widget.turnstileKey), )} finally { // Token jde použít jen jednou – pro další pokus si vyžádejte nový. token = null webView.evaluateJavascript(Turnstile.RESET_SCRIPT, null)}Token platí asi 5 minut a jde použít jen jednou: po každém odeslání, úspěšném i neúspěšném,
widget resetujte. Běžný widget potřebuje WebView zhruba 300 × 70 dp (kompaktní 150 × 140 dp).
Požadavky z aplikace nemají hlavičku Origin, takže jejich tokeny Frontmail ověří svým sdíleným
mobilním tajným klíčem. Vlastní klíče Turnstile k tomu nepotřebujete – se zapnutým Povolit
mobilní aplikace můžete CAPTCHA u šablon aplikace zapnout, i když organizace klíče zatím nemá.
Kompletní příklad (včetně Jetpack Compose a mostu omezeného na origin stránky přes
androidx.webkit) najdete v README SDK.
Vlastní site key
Sekce “Vlastní site key”Pokud chcete místo toho použít vlastní widget Turnstile, vytvořte ho přes TurnstileWidget.own:
val widget = TurnstileWidget.own(siteKey = "YOUR_TURNSTILE_SITE_KEY", baseUrl = "https://example.com")widget.turnstileKey je pak TurnstileKey.ORG, takže se token pošle s turnstile_key: "org"
a Frontmail ho ověří secret key ze Zabezpečení → Ochrana proti botům (Turnstile).
getStatus
Sekce “getStatus”val status = frontmail.getStatus(result) // nebo getStatus(messageId, statusToken)status.status // MessageStatus.SENT, DELIVERED, BOUNCED, …status.events // [MessageEvent(type = "accepted", at = "…"), …]Token se posílá v hlavičce X-Frontmail-Status-Token, nikdy v URL.
Opakování a idempotence
Sekce “Opakování a idempotence”Chyby sítě, vypršené časové limity, 5xx a 429 se automaticky opakují (s ohledem na
Retry-After; čekání delší než 60 sekund se už neopakuje) a všechny pokusy jednoho odeslání sdílejí
stejný idempotency key, takže opakování e-mail nikdy nepošle dvakrát. Ostatní chyby 4xx se
neopakují nikdy. Zrušení korutiny požadavek okamžitě zastaví. Viz
Retry a idempotence.
Chyby
Sekce “Chyby”Každé selhání je FrontmailException s vlastnostmi code, message, status (u chyb sítě
null), docsUrl, details a retryAfter a s kontrolami isAuth, isValidation,
isRateLimit, isInsufficientCredits a isNetwork. Kódy z klienta jsou network_error,
timeout, invalid_response a private_key_in_browser. Jak reagovat na jednotlivé kódy, popisuje
Zpracování chyb.
Dynamičtí příjemci
Sekce “Dynamičtí příjemci”Pokud To/CC/BCC šablony používají parametry, má odeslání s public key uzamčený obsah: šablona musí vyžadovat Turnstile, pole odesílatele musí být pevná, parametry nesmí obsahovat HTML, odkazy musí mířit na povolené domény a každý příjemce dostane za den jen omezený počet e-mailů.
Zabezpečení
Sekce “Zabezpečení”- Do aplikace patří jen public key (
pk_…). Cokoli v aplikaci se dá vytáhnout, takže private key v ní je jako zveřejněný. Viz Public key a private key. - Se zapnutým Povolit mobilní aplikace přijmeme požadavky bez hlavičky
Originodkudkoli – poslat je může jakýkoli skript, který zná váš public key. Spolehněte se na limit na IP adresu, Turnstile u všech šablon, které aplikace používá, a na seznam blokovaných hodnot.