@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 +21 -0
- package/README.md +172 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +2 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +1 -0
- package/build/core/mail-composer.d.ts +7 -0
- package/build/core/mail-composer.js +13 -0
- package/build/core/native-module.d.ts +7 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +27 -0
- package/build/core/types.js +1 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +3 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/index.d.ts +2 -0
- package/build-ngc/core/index.js +2 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/mail-composer.d.ts +7 -0
- package/build-ngc/core/mail-composer.js +14 -0
- package/build-ngc/core/mail-composer.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +7 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +27 -0
- package/build-ngc/core/types.js +2 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +40 -0
- package/package.json +150 -0
- package/src/angular/index.ts +2 -0
- package/src/core/index.ts +7 -0
- package/src/core/mail-composer.ts +23 -0
- package/src/core/native-module.ts +18 -0
- package/src/core/types.ts +31 -0
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 @@
|
|
|
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,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 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,cAAc,SAAS,CAAC"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
|
package/native-link.json
ADDED
|
@@ -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,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
|
+
};
|