Android (Kotlin)
dev.frontmail:frontmail umožní aplikácii pre Android (alebo akejkoľvek aplikácii v Kotline na JVM)
odosielať e-maily cez vaše šablóny s verejným kľúčom – bez vlastného backendu. Je to obyčajná
knižnica v Kotline so suspend API; jediné závislosti sú kotlinx-coroutines-core
a kotlinx-serialization-json.
Inštalácia
Section titled “Inštalácia”dependencies { implementation("dev.frontmail:frontmail:0.1.0")}Aplikácia potrebuje Kotlin 2.2 alebo novší a oprávnenie INTERNET:
<uses-permission android:name="android.permission.INTERNET" />Zdrojový kód a vydania: github.com/frontmail-dev/frontmail-kotlin.
Povoľte mobilné aplikácie
Section titled “Povoľte mobilné aplikácie”Natívne aplikácie neposielajú hlavičku Origin ani Referer, takže požiadavky z aplikácie (bez
ohľadu na to, či máte nastavené povolené weby) skončia chybou
403 origin_not_allowed, kým v dashboarde nezapnete
Zabezpečenie → Mobilné aplikácie → Povoliť mobilné aplikácie a nekliknete na Uložiť.
Pozrite Nastavenia zabezpečenia.
Požiadavky z webov sa naďalej kontrolujú podľa zoznamu, takže prepínač ochranu vašich webových formulárov neoslabí. Prepínač je potrebný aj pri prázdnom zozname.
Odoslanie
Section titled “Odoslanie”Vytvorte jedného klienta a používajte ho opakovane (je bezpečný pre viac vlákien). send je
suspend funkcia, takže ju volajte z korutiny, napr. z lifecycleScope alebo 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) "Prijaté – doručíme čo najskôr." else "Ďakujeme!" } catch (e: FrontmailException) { state.value = e.message }}params prijíma reťazce, čísla, booleany, null, vnorené mapy a zoznamy, polia a JsonElement;
existuje aj variant, ktorý berie JsonObject. Bez serviceId sa použije služba šablóny.
result.status je QUEUED (prijaté na doručenie) alebo HELD (prijaté, čaká na kredity).
result.statusToken slúži na neskoršie zistenie stavu doručenia.
Ak chcete e-mail poslať v niektorom jazyku šablóny, odovzdajte
SendOptions(locale = …) so značkou BCP 47 – jazyk zariadenia vráti
Locale.getDefault().toLanguageTag(). Pri neznámom jazyku sa použije rovnaký základný jazyk
a potom predvolený jazyk šablóny; result.locale je použitý jazyk:
val result = frontmail.send( "tpl_contact", mapOf("name" to name), options = SendOptions(locale = Locale.getDefault().toLanguageTag()),)Voľby klienta:
| Voľba | Predvolená hodnota | Význam |
|---|---|---|
apiUrl |
https://api.frontmail.dev |
základná URL API |
retry |
RetryPolicy() (3 opakovania) |
RetryPolicy(retries, baseDelayMillis, maxDelayMillis), RetryPolicy.NONE opakovanie vypne |
timeoutMillis |
15000 |
časový limit jedného pokusu |
Súkromný kľúč (sk_…) vyhodí FrontmailException s kódom private_key_in_browser už
v konštruktore – v aplikácii smie byť len verejný kľúč.
Každá požiadavka nesie hlavičku X-Frontmail-Client: frontmail-kotlin/<verzia>.
Prílohy
Section titled “Prílohy”Ak šablóna povoľuje dynamické prílohy, odovzdajte ich 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_…"), // nahrané vopred cez POST /v1/uploads ), ),)Turnstile
Section titled “Turnstile”Ak šablóna vyžaduje Turnstile, zobrazte widget Cloudflaru vo WebView
a token odovzdajte do send. SDK nemá žiadnu závislosť od Androidu, takže vám dá jednotlivé diely
a WebView si zapojíte sami:
Turnstile.sharedWidget()– zdieľaný mobilný kľúč Frontmailu. Site key nepotrebujete: SDK ho načíta zGET /v1/public-config(s cache, takže len raz) a widget pobeží ako vložená HTML stránka s adresouhttps://mobile.frontmail.dev– z tej adresy sa nič nesťahuje.widget.html()– stránka s widgetom. Správy posiela do JavaScriptového mostaFrontmailTurnstile(Turnstile.ANDROID_BRIDGE_NAME).Turnstile.parseMessage(data)– prijme len presne tie správy, ktoré stránka posiela; vrátiToken,Expired,Erroralebonull.widget.navigationDecision(url, isForMainFrame)– stráženie navigácie:ALLOW,OPEN_EXTERNALLY(odkazy vo widgete) aleboBLOCK.
@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 potom pošlite spolu s turnstileKey widgetu:
val widget = Turnstile.sharedWidget()webView.showTurnstile(widget, onToken = { token = it }, onExpire = { token = null })
// pri odoslaní:try { frontmail.send( "tpl_contact", mapOf("email" to email, "message" to message), options = SendOptions(turnstileToken = token, turnstileKey = widget.turnstileKey), )} finally { // Token sa dá použiť len raz – na ďalší pokus si vyžiadajte nový. token = null webView.evaluateJavascript(Turnstile.RESET_SCRIPT, null)}Token platí približne 5 minút a dá sa použiť len raz: po každom odoslaní, úspešnom aj
neúspešnom, widget resetujte. Bežný widget potrebuje WebView približne 300 × 70 dp (kompaktný
150 × 140 dp).
Požiadavky z aplikácie nemajú hlavičku Origin, takže ich tokeny Frontmail overí svojím zdieľaným
mobilným tajným kľúčom. Vlastné kľúče Turnstile na to nepotrebujete – so zapnutým Povoliť mobilné
aplikácie môžete CAPTCHA pri šablónach aplikácie zapnúť, aj keď organizácia kľúče zatiaľ nemá.
Kompletný príklad (vrátane Jetpack Compose a mosta obmedzeného na origin stránky cez
androidx.webkit) nájdete v README SDK.
Vlastný site key
Section titled “Vlastný site key”Ak chcete namiesto toho použiť vlastný widget Turnstile, vytvorte ho cez TurnstileWidget.own:
val widget = TurnstileWidget.own(siteKey = "YOUR_TURNSTILE_SITE_KEY", baseUrl = "https://example.com")widget.turnstileKey je potom TurnstileKey.ORG, takže sa token pošle s turnstile_key: "org"
a Frontmail ho overí secret key zo Zabezpečenie → Ochrana pred botmi (Turnstile).
getStatus
Section titled “getStatus”val status = frontmail.getStatus(result) // alebo getStatus(messageId, statusToken)status.status // MessageStatus.SENT, DELIVERED, BOUNCED, …status.events // [MessageEvent(type = "accepted", at = "…"), …]Token sa posiela v hlavičke X-Frontmail-Status-Token, nikdy v URL.
Opakovanie a idempotencia
Section titled “Opakovanie a idempotencia”Sieťové chyby, vypršané časové limity, 5xx a 429 sa automaticky opakujú (s ohľadom na
Retry-After; čakanie dlhšie ako 60 sekúnd sa už neopakuje) a všetky pokusy jedného odoslania
zdieľajú rovnaký idempotency key, takže opakovanie e-mail nikdy nepošle dvakrát. Ostatné chyby 4xx
sa neopakujú nikdy. Zrušenie korutiny požiadavku okamžite zastaví. Pozrite
Opakovanie a idempotencia.
Každé zlyhanie je FrontmailException s vlastnosťami code, message, status (pri sieťových
chybách null), docsUrl, details a retryAfter a s kontrolami isAuth, isValidation,
isRateLimit, isInsufficientCredits a isNetwork. Kódy z klienta sú network_error,
timeout, invalid_response a private_key_in_browser. Ako reagovať na jednotlivé kódy, opisuje
Spracovanie chýb.
Dynamickí príjemcovia
Section titled “Dynamickí príjemcovia”Ak To/CC/BCC šablóny používajú parametre, má odoslanie s verejným kľúčom uzamknutý obsah: šablóna musí vyžadovať Turnstile, polia odosielateľa musia byť pevné, parametre nesmú obsahovať HTML, odkazy musia smerovať na povolené domény a každý príjemca dostane za deň len obmedzený počet e-mailov.
Zabezpečenie
Section titled “Zabezpečenie”- Do aplikácie patrí len verejný kľúč (
pk_…). Čokoľvek v aplikácii sa dá vytiahnuť, takže súkromný kľúč v nej je ako zverejnený. Pozrite Verejný a súkromný kľúč. - So zapnutým Povoliť mobilné aplikácie prijmeme požiadavky bez hlavičky
Originodkiaľkoľvek – poslať ich môže akýkoľvek skript, ktorý pozná váš verejný kľúč. Spoľahnite sa na limit na IP adresu, Turnstile pri všetkých šablónach, ktoré aplikácia používa, a na zoznam blokovaných hodnôt.