@symbiote-native/mail-composer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @symbiote-native/mail-composer
2
+
3
+ Let users email you from inside the app: open the system mail composer with recipients, subject,
4
+ body and attachments already filled in. One API for every [SymbioteNative](../../README.md)
5
+ adapter (React, Vue, Svelte, Solid and Angular).
6
+
7
+ It wraps [`expo-mail-composer`](https://github.com/expo/expo/tree/main/packages/expo-mail-composer)
8
+ the same way [`@symbiote-native/sms`](../sms) wraps its upstream: `expo-modules-core` is a direct
9
+ dependency, never the `expo` meta-package, and the upstream JS is hand-ported into `core/`. The
10
+ mechanics live in the `symbiote-expo-native-module` project skill.
11
+
12
+ Nothing is ever sent on the user's behalf. Both platforms open their own composer with the draft
13
+ filled in; the user presses send, edits, or discards it.
14
+
15
+ ## Install
16
+
17
+ **New app:**
18
+
19
+ ```bash
20
+ npx @symbiote-native/cli new my-app --mail-composer
21
+ ```
22
+
23
+ **Existing SymbioteNative app:**
24
+
25
+ ```bash
26
+ npx @symbiote-native/cli add --mail-composer
27
+ ```
28
+
29
+ Either way: installs `@symbiote-native/mail-composer` and wires the native autolinking
30
+ automatically - see [`@symbiote-native/cli`](../cli).
31
+
32
+ <details>
33
+ <summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
34
+
35
+ ```bash
36
+ npm install @symbiote-native/mail-composer
37
+ ```
38
+
39
+ `expo-mail-composer` and `expo-modules-core` come along as regular, pinned dependencies - never
40
+ install either yourself, and never add the `expo` meta-package to this project (it bundles its
41
+ own Metro/Babel pipeline that conflicts with this project's own).
42
+
43
+ ### Required one-time step: native autolinking wiring
44
+
45
+ Unlike a plain RN native module, `expo-mail-composer`'s native code is discovered by
46
+ `expo-modules-autolinking` - this needs wiring into the native host app **once**, covering this
47
+ package and every other `expo-modules-core` package with zero further changes:
48
+
49
+ | Platform | Touches |
50
+ | -------- | ------------------------------------------------------------------------------------- |
51
+ | iOS | `ios/Podfile` - add `use_expo_modules!` |
52
+ | iOS | `AppDelegate.swift` - Expo's runtime-bootstrap hook |
53
+ | Android | `settings.gradle` / `app/build.gradle` - resolve and include the Expo Gradle projects |
54
+ | Android | `MainApplication.kt` - Expo's bootstrap hook, plus a native-module name map |
55
+
56
+ Full mechanics live in the `symbiote-expo-native-module` skill. The per-package half of that
57
+ table - the Gradle dependency and the module map entry - is generated by
58
+ [`@symbiote-native/expo-modules-link`](../expo-modules-link) from this package's
59
+ `native-link.json` on every install.
60
+
61
+ ### One Info.plist array key, no permissions, no config plugin
62
+
63
+ - **No runtime permission on either platform.** Composing is a `MFMailComposeViewController` on
64
+ iOS and an `ACTION_SENDTO` chooser on Android - the user's own Mail app sends it.
65
+ - **`LSApplicationQueriesSchemes` (iOS only).** `getClients()` checks each candidate mail app's
66
+ URL scheme with `canOpenURL`, which iOS 9+ silently refuses for an undeclared scheme - so
67
+ `native-link.json` carries the same 22-scheme list upstream's config plugin writes, generated
68
+ into your `Info.plist` automatically (merges additively with any other package's own entries).
69
+ - **No manifest edit.** `expo-mail-composer` ships its own `<queries>` block for Android 11+
70
+ package-visibility rules; it merges in automatically once the Gradle project is included.
71
+ - **No config plugin beyond the Info.plist key above.**
72
+
73
+ </details>
74
+
75
+ ## Shape
76
+
77
+ ```
78
+ src/core/ the whole API: composeAsync + isAvailableAsync + getClients.
79
+ native-module.ts resolves ExpoMailComposer through
80
+ expo-modules-core's requireNativeModule.
81
+ src/angular/ @symbiote-native/mail-composer/angular
82
+ ```
83
+
84
+ `./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core/` -
85
+ no physical per-framework file. All three exports are stateless free functions with no event
86
+ stream, so there is nothing for a hook, composable, or service to wrap - the same reason
87
+ [`@symbiote-native/sms`](../sms) does the same. `./angular` stays a physical file/subpath since
88
+ Angular ships through a separate `ngc`/AOT build (`build-ngc/`). Import from
89
+ `@symbiote-native/mail-composer` directly if you don't care which adapter you're on; the
90
+ per-adapter subpaths exist so every wrapper package has the same import surface.
91
+
92
+ ## Use it
93
+
94
+ ```ts
95
+ import { composeAsync, isAvailableAsync } from '@symbiote-native/mail-composer';
96
+
97
+ if (await isAvailableAsync()) {
98
+ const { status } = await composeAsync({
99
+ recipients: ['support@example.com'],
100
+ subject: 'Order #1234',
101
+ body: 'Hi, I have a question about my order.',
102
+ });
103
+ // 'sent' | 'saved' | 'cancelled' on iOS; always 'sent' on Android
104
+ }
105
+ ```
106
+
107
+ Listing installed mail clients (iOS reports each one's URL scheme; Android its package name):
108
+
109
+ ```ts
110
+ import { getClients } from '@symbiote-native/mail-composer';
111
+
112
+ const clients = getClients();
113
+ // [{ label: 'Gmail', url: 'googlegmail://' }, ...] on iOS
114
+ // [{ label: 'Gmail', packageName: 'com.google.android.gm' }, ...] on Android
115
+ ```
116
+
117
+ With an attachment:
118
+
119
+ ```ts
120
+ await composeAsync({
121
+ recipients: ['support@example.com'],
122
+ attachments: ['file:///path/to/receipt.pdf'],
123
+ });
124
+ ```
125
+
126
+ ## API
127
+
128
+ | Export | Signature | Notes |
129
+ | ------------------ | ------------------------------------------------------ | ------------------------------------------------------------------- |
130
+ | `isAvailableAsync` | `() => Promise<boolean>` | `false` on iOS under an MDM profile blocking outgoing mail. |
131
+ | `composeAsync` | `(options: IMailComposerOptions) => Promise<IMailComposerResult>` | Opens the composer. Resolves when it closes. |
132
+ | `getClients` | `() => IMailClient[]` | Synchronous - no native round-trip needed. |
133
+
134
+ `IMailComposerOptions`: `recipients` / `ccRecipients` / `bccRecipients` (string arrays), `subject`,
135
+ `body`, `isHtml`, `attachments` (app-internal file URIs).
136
+ `IMailComposerResult`: `{ status: 'undetermined' | 'sent' | 'saved' | 'cancelled' }`.
137
+ `IMailClient`: `label`, plus `url` (iOS) or `packageName` (Android).
138
+
139
+ ## Notes
140
+
141
+ - **Check `isAvailableAsync()` first.** An iOS simulator cannot sign in to a mail account, so the composer
142
+ never opens there; offer a fallback such as copying the address.
143
+ - **Attachments must live in the app's own storage** (`file://` URIs, for example from the cache directory).
144
+ - **Android always resolves `sent`.** It has no way to read the composer's real outcome back -
145
+ upstream reports the same fixed status there.
146
+ - **iOS needs a signed-in Mail app.** `isAvailableAsync`/`composeAsync` fail without one -
147
+ the same check `MFMailComposeViewController.canSendMail()` makes natively.
148
+
149
+ ## Common questions
150
+
151
+ - **Nothing happens on the iOS Simulator.** It cannot sign in to a mail account; use a device.
152
+ - **`isAvailableAsync()` is `true` on Android but no composer opens.** The Android check is weak and
153
+ some devices show a plain share sheet; offer a fallback such as copying the address.
154
+ - **HTML body shows as text on Android.** `isHtml` is not reliable across Android mail apps.
155
+ - **Sent or not?** iOS reports `sent` / `saved` / `cancelled`; Android always reports `sent`.
156
+ - **Attachment missing.** Attach files from the app's own storage via `file://` URIs.
157
+
158
+ Sources: [Expo docs: MailComposer](https://docs.expo.dev/versions/latest/sdk/mail-composer/),
159
+ [expo/expo#35466](https://github.com/expo/expo/issues/35466),
160
+ [expo/expo#37740](https://github.com/expo/expo/issues/37740),
161
+ [expo/expo#16690](https://github.com/expo/expo/issues/16690),
162
+ [expo/expo#20509](https://github.com/expo/expo/issues/20509).
163
+
164
+ ## Test it
165
+
166
+ ```bash
167
+ pnpm vitest run packages/mail-composer
168
+ ```
169
+
170
+ The core tests fake the native module in place of `requireNativeModule`'s runtime resolution -
171
+ `ExpoMailComposer` only exists on a device, so a headless run would otherwise throw at import. The
172
+ composer itself, and the `sent`/`saved`/`cancelled` distinction, can only be verified on a device.
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,2 @@
1
+ // No per-instance state or event stream to wrap in an injectable service — plain re-export.
2
+ export * from '../core/index.js';
@@ -0,0 +1,2 @@
1
+ export { composeAsync, getClients, isAvailableAsync } from './mail-composer';
2
+ export type { IMailClient, IMailComposerOptions, IMailComposerResult, IMailComposerStatus, } from './types';
@@ -0,0 +1 @@
1
+ export { composeAsync, getClients, isAvailableAsync } from './mail-composer.js';
@@ -0,0 +1,7 @@
1
+ import type { IMailClient, IMailComposerOptions, IMailComposerResult } from './types';
2
+ /** Mail clients installed on the device — present as choices, or open one to check a reply. */
3
+ export declare function getClients(): IMailClient[];
4
+ /** Opens a mail modal (iOS) / chooser (Android). Android always resolves `{ status: 'sent' }`. */
5
+ export declare function composeAsync(options: IMailComposerOptions): Promise<IMailComposerResult>;
6
+ /** Whether `composeAsync` can be used — `false` on iOS under an MDM outgoing-mail block. */
7
+ export declare function isAvailableAsync(): Promise<boolean>;
@@ -0,0 +1,13 @@
1
+ import { expoMailComposer } from './native-module.js';
2
+ /** Mail clients installed on the device — present as choices, or open one to check a reply. */
3
+ export function getClients() {
4
+ return expoMailComposer.getClients();
5
+ }
6
+ /** Opens a mail modal (iOS) / chooser (Android). Android always resolves `{ status: 'sent' }`. */
7
+ export async function composeAsync(options) {
8
+ return expoMailComposer.composeAsync(options);
9
+ }
10
+ /** Whether `composeAsync` can be used — `false` on iOS under an MDM outgoing-mail block. */
11
+ export async function isAvailableAsync() {
12
+ return expoMailComposer.isAvailableAsync();
13
+ }
@@ -0,0 +1,7 @@
1
+ import type { IMailClient, IMailComposerOptions, IMailComposerResult } from './types';
2
+ export type INativeMailComposerModule = {
3
+ isAvailableAsync(): Promise<boolean>;
4
+ composeAsync(options: IMailComposerOptions): Promise<IMailComposerResult>;
5
+ getClients(): IMailClient[];
6
+ };
7
+ export declare const expoMailComposer: INativeMailComposerModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_MAIL_COMPOSER_MODULE_NAME = 'ExpoMailComposer';
3
+ export const expoMailComposer = requireNativeModule(EXPO_MAIL_COMPOSER_MODULE_NAME);
@@ -0,0 +1,27 @@
1
+ /** A mail client installed on the device. */
2
+ export type IMailClient = {
3
+ /** The display name of the mail client. */
4
+ label: string;
5
+ /** The package name of the mail client application. @platform android */
6
+ packageName?: string;
7
+ /** The URL scheme of the mail client. @platform ios */
8
+ url?: string;
9
+ };
10
+ export type IMailComposerOptions = {
11
+ /** E-mail addresses of the recipients. */
12
+ recipients?: string[];
13
+ /** E-mail addresses of the CC recipients. */
14
+ ccRecipients?: string[];
15
+ /** E-mail addresses of the BCC recipients. */
16
+ bccRecipients?: string[];
17
+ subject?: string;
18
+ body?: string;
19
+ /** Whether `body` contains HTML tags. Not fully supported on Android. */
20
+ isHtml?: boolean;
21
+ /** App-internal file URIs to attach. */
22
+ attachments?: string[];
23
+ };
24
+ export type IMailComposerStatus = 'undetermined' | 'sent' | 'saved' | 'cancelled';
25
+ export type IMailComposerResult = {
26
+ status: IMailComposerStatus;
27
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,3 @@
1
+ // No per-instance state or event stream to wrap in an injectable service — plain re-export.
2
+ export * from '../core';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,cAAc,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { composeAsync, getClients, isAvailableAsync } from './mail-composer';
2
+ export type { IMailClient, IMailComposerOptions, IMailComposerResult, IMailComposerStatus, } from './types';
@@ -0,0 +1,2 @@
1
+ export { composeAsync, getClients, isAvailableAsync } from './mail-composer';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { IMailClient, IMailComposerOptions, IMailComposerResult } from './types';
2
+ /** Mail clients installed on the device — present as choices, or open one to check a reply. */
3
+ export declare function getClients(): IMailClient[];
4
+ /** Opens a mail modal (iOS) / chooser (Android). Android always resolves `{ status: 'sent' }`. */
5
+ export declare function composeAsync(options: IMailComposerOptions): Promise<IMailComposerResult>;
6
+ /** Whether `composeAsync` can be used — `false` on iOS under an MDM outgoing-mail block. */
7
+ export declare function isAvailableAsync(): Promise<boolean>;
@@ -0,0 +1,14 @@
1
+ import { expoMailComposer } from './native-module';
2
+ /** Mail clients installed on the device — present as choices, or open one to check a reply. */
3
+ export function getClients() {
4
+ return expoMailComposer.getClients();
5
+ }
6
+ /** Opens a mail modal (iOS) / chooser (Android). Android always resolves `{ status: 'sent' }`. */
7
+ export async function composeAsync(options) {
8
+ return expoMailComposer.composeAsync(options);
9
+ }
10
+ /** Whether `composeAsync` can be used — `false` on iOS under an MDM outgoing-mail block. */
11
+ export async function isAvailableAsync() {
12
+ return expoMailComposer.isAvailableAsync();
13
+ }
14
+ //# sourceMappingURL=mail-composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail-composer.js","sourceRoot":"","sources":["../../src/core/mail-composer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAOnD,+FAA+F;AAC/F,MAAM,UAAU,UAAU;IACxB,OAAO,gBAAgB,CAAC,UAAU,EAAE,CAAC;AACvC,CAAC;AAED,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAA6B;IAE7B,OAAO,gBAAgB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,OAAO,gBAAgB,CAAC,gBAAgB,EAAE,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { IMailClient, IMailComposerOptions, IMailComposerResult } from './types';
2
+ export type INativeMailComposerModule = {
3
+ isAvailableAsync(): Promise<boolean>;
4
+ composeAsync(options: IMailComposerOptions): Promise<IMailComposerResult>;
5
+ getClients(): IMailClient[];
6
+ };
7
+ export declare const expoMailComposer: INativeMailComposerModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_MAIL_COMPOSER_MODULE_NAME = 'ExpoMailComposer';
3
+ export const expoMailComposer = requireNativeModule(EXPO_MAIL_COMPOSER_MODULE_NAME);
4
+ //# sourceMappingURL=native-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAOxD,MAAM,8BAA8B,GAAG,kBAAkB,CAAC;AAQ1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CACjD,8BAA8B,CAC/B,CAAC"}
@@ -0,0 +1,27 @@
1
+ /** A mail client installed on the device. */
2
+ export type IMailClient = {
3
+ /** The display name of the mail client. */
4
+ label: string;
5
+ /** The package name of the mail client application. @platform android */
6
+ packageName?: string;
7
+ /** The URL scheme of the mail client. @platform ios */
8
+ url?: string;
9
+ };
10
+ export type IMailComposerOptions = {
11
+ /** E-mail addresses of the recipients. */
12
+ recipients?: string[];
13
+ /** E-mail addresses of the CC recipients. */
14
+ ccRecipients?: string[];
15
+ /** E-mail addresses of the BCC recipients. */
16
+ bccRecipients?: string[];
17
+ subject?: string;
18
+ body?: string;
19
+ /** Whether `body` contains HTML tags. Not fully supported on Android. */
20
+ isHtml?: boolean;
21
+ /** App-internal file URIs to attach. */
22
+ attachments?: string[];
23
+ };
24
+ export type IMailComposerStatus = 'undetermined' | 'sent' | 'saved' | 'cancelled';
25
+ export type IMailComposerResult = {
26
+ status: IMailComposerStatus;
27
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ {
2
+ "android": {
3
+ "gradleProjectName": "expo-mail-composer",
4
+ "modules": [
5
+ {
6
+ "importPath": "expo.modules.mailcomposer.MailComposerModule",
7
+ "className": "MailComposerModule",
8
+ "nativeName": "ExpoMailComposer"
9
+ }
10
+ ]
11
+ },
12
+ "ios": {
13
+ "infoPlistArrayKeys": {
14
+ "LSApplicationQueriesSchemes": [
15
+ "airmail",
16
+ "message",
17
+ "bluemail",
18
+ "canary",
19
+ "edisonmail",
20
+ "szn-email",
21
+ "fastmail",
22
+ "x-gmxmail-netid-v1",
23
+ "googlegmail",
24
+ "mailrumail",
25
+ "ms-outlook",
26
+ "protonmail",
27
+ "ctxmail",
28
+ "readdle-spark",
29
+ "superhuman",
30
+ "telekommail",
31
+ "tutanota",
32
+ "x-webdemail-netid-v1",
33
+ "ymail",
34
+ "yandexmail",
35
+ "appcenter-f45b4c0b-75c9-2d01-7ab6-41f6a6015be2",
36
+ "mymail-mailto"
37
+ ]
38
+ }
39
+ }
40
+ }
package/package.json ADDED
@@ -0,0 +1,150 @@
1
+ {
2
+ "name": "@symbiote-native/mail-composer",
3
+ "version": "0.1.0",
4
+ "description": "expo-mail-composer wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Opens the system mail composer prefilled with recipients, subject, body, and attachments.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "react-native",
8
+ "symbiote-native",
9
+ "expo-mail-composer",
10
+ "react",
11
+ "vue",
12
+ "svelte",
13
+ "solid",
14
+ "angular",
15
+ "email",
16
+ "mail-composer"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
21
+ "directory": "packages/mail-composer"
22
+ },
23
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/mail-composer#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
26
+ },
27
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
28
+ "type": "module",
29
+ "main": "./build/core/index.js",
30
+ "module": "./build/core/index.js",
31
+ "types": "./build/core/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./build/core/index.d.ts",
35
+ "default": "./build/core/index.js"
36
+ },
37
+ "./vue": {
38
+ "types": "./build/core/index.d.ts",
39
+ "default": "./build/core/index.js"
40
+ },
41
+ "./react": {
42
+ "types": "./build/core/index.d.ts",
43
+ "default": "./build/core/index.js"
44
+ },
45
+ "./svelte": {
46
+ "types": "./build/core/index.d.ts",
47
+ "default": "./build/core/index.js"
48
+ },
49
+ "./solid": {
50
+ "types": "./build/core/index.d.ts",
51
+ "default": "./build/core/index.js"
52
+ },
53
+ "./angular": {
54
+ "types": "./build-ngc/angular/index.d.ts",
55
+ "react-native": "./build-ngc/angular/index.js",
56
+ "default": "./src/angular/index.ts"
57
+ }
58
+ },
59
+ "files": [
60
+ "src",
61
+ "build",
62
+ "build-ngc",
63
+ "native-link.json",
64
+ "!src/**/*.test.*",
65
+ "!src/**/*.spec.*",
66
+ "!src/**/*.detox.*"
67
+ ],
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "dependencies": {
72
+ "expo-mail-composer": "57.0.2",
73
+ "expo-modules-core": "57.0.20"
74
+ },
75
+ "peerDependencies": {
76
+ "@angular/core": ">=20",
77
+ "@vue/runtime-core": "^3.5.13",
78
+ "react": ">=19.0.0",
79
+ "react-native": ">=0.86",
80
+ "solid-js": ">=1.9.0",
81
+ "svelte": ">=5.56.0",
82
+ "vue": ">=3.5.0",
83
+ "@symbiote-native/angular": "^3.1.3",
84
+ "@symbiote-native/engine": "^1.4.0",
85
+ "@symbiote-native/react": "^3.1.0",
86
+ "@symbiote-native/vue": "^3.1.0",
87
+ "@symbiote-native/solid": "^3.0.5",
88
+ "@symbiote-native/svelte": "^3.0.5"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "@symbiote-native/angular": {
92
+ "optional": true
93
+ },
94
+ "@symbiote-native/react": {
95
+ "optional": true
96
+ },
97
+ "@symbiote-native/solid": {
98
+ "optional": true
99
+ },
100
+ "@symbiote-native/svelte": {
101
+ "optional": true
102
+ },
103
+ "@symbiote-native/vue": {
104
+ "optional": true
105
+ },
106
+ "@angular/core": {
107
+ "optional": true
108
+ },
109
+ "@vue/runtime-core": {
110
+ "optional": true
111
+ },
112
+ "react": {
113
+ "optional": true
114
+ },
115
+ "solid-js": {
116
+ "optional": true
117
+ },
118
+ "svelte": {
119
+ "optional": true
120
+ },
121
+ "vue": {
122
+ "optional": true
123
+ }
124
+ },
125
+ "devDependencies": {
126
+ "@angular/compiler": "~22.0.8",
127
+ "@angular/compiler-cli": "~22.0.8",
128
+ "@angular/core": "~22.0.8",
129
+ "@types/node": "^26.0.0",
130
+ "@types/react": "^19.2.0",
131
+ "@vue/runtime-core": "^3.5.13",
132
+ "react": "19.2.3",
133
+ "solid-js": "^1.9.14",
134
+ "svelte": "^5.56.0",
135
+ "typescript": "~6.0.0",
136
+ "@symbiote-native/angular": "3.1.3",
137
+ "@symbiote-native/react": "3.1.0",
138
+ "@symbiote-native/engine": "1.4.0",
139
+ "@symbiote-native/solid": "3.0.5",
140
+ "@symbiote-native/test-utils": "0.4.5",
141
+ "@symbiote-native/vue": "3.1.0",
142
+ "@symbiote-native/svelte": "3.0.5"
143
+ },
144
+ "scripts": {
145
+ "typecheck": "tsc --build",
146
+ "clean": "rm -rf build-ngc",
147
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
148
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
149
+ }
150
+ }
@@ -0,0 +1,2 @@
1
+ // No per-instance state or event stream to wrap in an injectable service — plain re-export.
2
+ export * from '../core';
@@ -0,0 +1,7 @@
1
+ export { composeAsync, getClients, isAvailableAsync } from './mail-composer';
2
+ export type {
3
+ IMailClient,
4
+ IMailComposerOptions,
5
+ IMailComposerResult,
6
+ IMailComposerStatus,
7
+ } from './types';
@@ -0,0 +1,23 @@
1
+ import { expoMailComposer } from './native-module';
2
+ import type {
3
+ IMailClient,
4
+ IMailComposerOptions,
5
+ IMailComposerResult,
6
+ } from './types';
7
+
8
+ /** Mail clients installed on the device — present as choices, or open one to check a reply. */
9
+ export function getClients(): IMailClient[] {
10
+ return expoMailComposer.getClients();
11
+ }
12
+
13
+ /** Opens a mail modal (iOS) / chooser (Android). Android always resolves `{ status: 'sent' }`. */
14
+ export async function composeAsync(
15
+ options: IMailComposerOptions,
16
+ ): Promise<IMailComposerResult> {
17
+ return expoMailComposer.composeAsync(options);
18
+ }
19
+
20
+ /** Whether `composeAsync` can be used — `false` on iOS under an MDM outgoing-mail block. */
21
+ export async function isAvailableAsync(): Promise<boolean> {
22
+ return expoMailComposer.isAvailableAsync();
23
+ }
@@ -0,0 +1,18 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ import type {
3
+ IMailClient,
4
+ IMailComposerOptions,
5
+ IMailComposerResult,
6
+ } from './types';
7
+
8
+ const EXPO_MAIL_COMPOSER_MODULE_NAME = 'ExpoMailComposer';
9
+
10
+ export type INativeMailComposerModule = {
11
+ isAvailableAsync(): Promise<boolean>;
12
+ composeAsync(options: IMailComposerOptions): Promise<IMailComposerResult>;
13
+ getClients(): IMailClient[];
14
+ };
15
+
16
+ export const expoMailComposer = requireNativeModule<INativeMailComposerModule>(
17
+ EXPO_MAIL_COMPOSER_MODULE_NAME,
18
+ );
@@ -0,0 +1,31 @@
1
+ /** A mail client installed on the device. */
2
+ export type IMailClient = {
3
+ /** The display name of the mail client. */
4
+ label: string;
5
+ /** The package name of the mail client application. @platform android */
6
+ packageName?: string;
7
+ /** The URL scheme of the mail client. @platform ios */
8
+ url?: string;
9
+ };
10
+
11
+ export type IMailComposerOptions = {
12
+ /** E-mail addresses of the recipients. */
13
+ recipients?: string[];
14
+ /** E-mail addresses of the CC recipients. */
15
+ ccRecipients?: string[];
16
+ /** E-mail addresses of the BCC recipients. */
17
+ bccRecipients?: string[];
18
+ subject?: string;
19
+ body?: string;
20
+ /** Whether `body` contains HTML tags. Not fully supported on Android. */
21
+ isHtml?: boolean;
22
+ /** App-internal file URIs to attach. */
23
+ attachments?: string[];
24
+ };
25
+
26
+ export type IMailComposerStatus =
27
+ 'undetermined' | 'sent' | 'saved' | 'cancelled';
28
+
29
+ export type IMailComposerResult = {
30
+ status: IMailComposerStatus;
31
+ };