@intelligo-dev/core 1.0.0-beta.1 → 1.0.0-beta.13
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/NOTICE +6 -0
- package/README.md +102 -0
- package/dist/attachments/errors.d.ts +7 -0
- package/dist/attachments/errors.d.ts.map +1 -0
- package/dist/attachments/errors.js +11 -0
- package/dist/attachments/errors.js.map +1 -0
- package/dist/attachments/index.d.ts +8 -0
- package/dist/attachments/index.d.ts.map +1 -0
- package/dist/attachments/index.js +7 -0
- package/dist/attachments/index.js.map +1 -0
- package/dist/attachments/service.d.ts +45 -0
- package/dist/attachments/service.d.ts.map +1 -0
- package/dist/attachments/service.js +100 -0
- package/dist/attachments/service.js.map +1 -0
- package/dist/attachments/types.d.ts +6 -0
- package/dist/attachments/types.d.ts.map +1 -0
- package/dist/attachments/types.js +2 -0
- package/dist/attachments/types.js.map +1 -0
- package/dist/conversations/errors.d.ts +2 -8
- package/dist/conversations/errors.d.ts.map +1 -1
- package/dist/conversations/errors.js +2 -8
- package/dist/conversations/errors.js.map +1 -1
- package/dist/conversations/index.d.ts +4 -12
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/index.js +2 -11
- package/dist/conversations/index.js.map +1 -1
- package/dist/conversations/service.d.ts +49 -61
- package/dist/conversations/service.d.ts.map +1 -1
- package/dist/conversations/service.js +110 -83
- package/dist/conversations/service.js.map +1 -1
- package/dist/conversations/types.d.ts +1 -4
- package/dist/conversations/types.d.ts.map +1 -1
- package/dist/conversations/types.js +0 -3
- package/dist/conversations/types.js.map +1 -1
- package/dist/db/client.d.ts +21 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +43 -18
- package/dist/db/client.js.map +1 -1
- package/dist/db/index.d.ts +3 -3
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +3 -7
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/agents.d.ts +14 -51
- package/dist/db/schema/agents.d.ts.map +1 -1
- package/dist/db/schema/agents.js +9 -21
- package/dist/db/schema/agents.js.map +1 -1
- package/dist/db/schema/ai.d.ts +86 -1556
- package/dist/db/schema/ai.d.ts.map +1 -1
- package/dist/db/schema/ai.js +32 -236
- package/dist/db/schema/ai.js.map +1 -1
- package/dist/db/schema/auth.d.ts +5 -34
- package/dist/db/schema/auth.d.ts.map +1 -1
- package/dist/db/schema/auth.js +25 -56
- package/dist/db/schema/auth.js.map +1 -1
- package/dist/db/schema/billing.d.ts +136 -107
- package/dist/db/schema/billing.d.ts.map +1 -1
- package/dist/db/schema/billing.js +72 -60
- package/dist/db/schema/billing.js.map +1 -1
- package/dist/db/schema/feature-flags.d.ts +3 -12
- package/dist/db/schema/feature-flags.d.ts.map +1 -1
- package/dist/db/schema/feature-flags.js +3 -12
- package/dist/db/schema/feature-flags.js.map +1 -1
- package/dist/db/schema/identity.d.ts +12 -274
- package/dist/db/schema/identity.d.ts.map +1 -1
- package/dist/db/schema/identity.js +19 -104
- package/dist/db/schema/identity.js.map +1 -1
- package/dist/db/schema/index.d.ts +8 -15
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/index.js +12 -42
- package/dist/db/schema/index.js.map +1 -1
- package/dist/db/schema/notifications.d.ts +3 -17
- package/dist/db/schema/notifications.d.ts.map +1 -1
- package/dist/db/schema/notifications.js +3 -17
- package/dist/db/schema/notifications.js.map +1 -1
- package/dist/db/schema/usage.d.ts +94 -77
- package/dist/db/schema/usage.d.ts.map +1 -1
- package/dist/db/schema/usage.js +59 -89
- package/dist/db/schema/usage.js.map +1 -1
- package/dist/db/validate.d.ts +2 -11
- package/dist/db/validate.d.ts.map +1 -1
- package/dist/db/validate.js +6 -17
- package/dist/db/validate.js.map +1 -1
- package/dist/db/workspace-queries.d.ts +5 -29
- package/dist/db/workspace-queries.d.ts.map +1 -1
- package/dist/db/workspace-queries.js +5 -29
- package/dist/db/workspace-queries.js.map +1 -1
- package/dist/documents/classifier.d.ts +5 -12
- package/dist/documents/classifier.d.ts.map +1 -1
- package/dist/documents/classifier.js +16 -16
- package/dist/documents/classifier.js.map +1 -1
- package/dist/documents/errors.d.ts +2 -10
- package/dist/documents/errors.d.ts.map +1 -1
- package/dist/documents/errors.js +2 -10
- package/dist/documents/errors.js.map +1 -1
- package/dist/documents/index.d.ts +4 -13
- package/dist/documents/index.d.ts.map +1 -1
- package/dist/documents/index.js +3 -12
- package/dist/documents/index.js.map +1 -1
- package/dist/documents/service.d.ts +19 -26
- package/dist/documents/service.d.ts.map +1 -1
- package/dist/documents/service.js +44 -38
- package/dist/documents/service.js.map +1 -1
- package/dist/documents/types.d.ts +6 -10
- package/dist/documents/types.d.ts.map +1 -1
- package/dist/documents/types.js +0 -3
- package/dist/documents/types.js.map +1 -1
- package/dist/email/brand.d.ts +17 -0
- package/dist/email/brand.d.ts.map +1 -0
- package/dist/email/brand.js +9 -0
- package/dist/email/brand.js.map +1 -0
- package/dist/email/index.d.ts +4 -4
- package/dist/email/index.js +4 -4
- package/dist/email/provider.d.ts +1 -7
- package/dist/email/provider.d.ts.map +1 -1
- package/dist/email/provider.js +10 -19
- package/dist/email/provider.js.map +1 -1
- package/dist/email/send.d.ts +12 -12
- package/dist/email/send.d.ts.map +1 -1
- package/dist/email/send.js +5 -19
- package/dist/email/send.js.map +1 -1
- package/dist/email/senders.d.ts +17 -30
- package/dist/email/senders.d.ts.map +1 -1
- package/dist/email/senders.js +33 -47
- package/dist/email/senders.js.map +1 -1
- package/dist/email/templates/base-layout.d.ts +1 -1
- package/dist/email/templates/base-layout.d.ts.map +1 -1
- package/dist/email/templates/base-layout.js +4 -6
- package/dist/email/templates/base-layout.js.map +1 -1
- package/dist/email/templates/index.d.ts +10 -10
- package/dist/email/templates/index.d.ts.map +1 -1
- package/dist/email/templates/index.js +10 -16
- package/dist/email/templates/index.js.map +1 -1
- package/dist/email/templates/password-reset.d.ts +2 -2
- package/dist/email/templates/password-reset.d.ts.map +1 -1
- package/dist/email/templates/password-reset.js +10 -22
- package/dist/email/templates/password-reset.js.map +1 -1
- package/dist/email/templates/payment-failed.d.ts +2 -2
- package/dist/email/templates/payment-failed.d.ts.map +1 -1
- package/dist/email/templates/payment-failed.js +11 -24
- package/dist/email/templates/payment-failed.js.map +1 -1
- package/dist/email/templates/quota-warning.d.ts +4 -4
- package/dist/email/templates/quota-warning.d.ts.map +1 -1
- package/dist/email/templates/quota-warning.js +19 -39
- package/dist/email/templates/quota-warning.js.map +1 -1
- package/dist/email/templates/subscription-confirmed.d.ts +2 -2
- package/dist/email/templates/subscription-confirmed.d.ts.map +1 -1
- package/dist/email/templates/subscription-confirmed.js +13 -28
- package/dist/email/templates/subscription-confirmed.js.map +1 -1
- package/dist/email/templates/trial-expiry.d.ts +2 -2
- package/dist/email/templates/trial-expiry.d.ts.map +1 -1
- package/dist/email/templates/trial-expiry.js +12 -36
- package/dist/email/templates/trial-expiry.js.map +1 -1
- package/dist/email/templates/trial-warning.d.ts +2 -2
- package/dist/email/templates/trial-warning.d.ts.map +1 -1
- package/dist/email/templates/trial-warning.js +15 -32
- package/dist/email/templates/trial-warning.js.map +1 -1
- package/dist/email/templates/verify-email.d.ts +2 -2
- package/dist/email/templates/verify-email.d.ts.map +1 -1
- package/dist/email/templates/verify-email.js +10 -22
- package/dist/email/templates/verify-email.js.map +1 -1
- package/dist/email/templates/welcome.d.ts +2 -2
- package/dist/email/templates/welcome.d.ts.map +1 -1
- package/dist/email/templates/welcome.js +13 -20
- package/dist/email/templates/welcome.js.map +1 -1
- package/dist/email/templates/workspace-invitation.d.ts +2 -2
- package/dist/email/templates/workspace-invitation.d.ts.map +1 -1
- package/dist/email/templates/workspace-invitation.js +12 -26
- package/dist/email/templates/workspace-invitation.js.map +1 -1
- package/dist/env.d.ts +5 -10
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +41 -31
- package/dist/env.js.map +1 -1
- package/dist/identity/audit.d.ts +11 -24
- package/dist/identity/audit.d.ts.map +1 -1
- package/dist/identity/audit.js +13 -27
- package/dist/identity/audit.js.map +1 -1
- package/dist/identity/errors.d.ts +4 -12
- package/dist/identity/errors.d.ts.map +1 -1
- package/dist/identity/errors.js +2 -8
- package/dist/identity/errors.js.map +1 -1
- package/dist/identity/index.d.ts +5 -13
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +4 -12
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/service.d.ts +13 -47
- package/dist/identity/service.d.ts.map +1 -1
- package/dist/identity/service.js +31 -78
- package/dist/identity/service.js.map +1 -1
- package/dist/identity/types.d.ts +1 -4
- package/dist/identity/types.d.ts.map +1 -1
- package/dist/identity/types.js +0 -3
- package/dist/identity/types.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -10
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +7 -18
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +45 -49
- package/dist/logger.js.map +1 -1
- package/dist/money.d.ts +95 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +208 -0
- package/dist/money.js.map +1 -0
- package/dist/notifications/index.d.ts +9 -43
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +9 -49
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/triggers.d.ts +9 -32
- package/dist/notifications/triggers.d.ts.map +1 -1
- package/dist/notifications/triggers.js +21 -58
- package/dist/notifications/triggers.js.map +1 -1
- package/dist/notifications/types.d.ts +1 -10
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/notifications/types.js +0 -9
- package/dist/notifications/types.js.map +1 -1
- package/dist/prompt.d.ts +38 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +118 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +60 -0
- package/dist/registry.js.map +1 -0
- package/dist/request-context.d.ts +49 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +100 -0
- package/dist/request-context.js.map +1 -0
- package/dist/storage/index.d.ts +65 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +109 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +74 -24
- package/src/attachments/errors.ts +18 -0
- package/src/attachments/index.ts +22 -0
- package/src/attachments/service.ts +178 -0
- package/src/attachments/types.ts +6 -0
- package/src/conversations/errors.ts +45 -0
- package/src/conversations/index.ts +39 -0
- package/src/conversations/service.ts +594 -0
- package/src/conversations/types.ts +15 -0
- package/src/db/client.ts +91 -0
- package/src/db/index.ts +3 -0
- package/src/db/migrations/0000_baseline.sql +652 -0
- package/src/db/migrations/0001_reconcile_chain_built_databases.sql +53 -0
- package/src/db/migrations/0002_webhook_claim_lease.sql +1 -0
- package/src/db/migrations/README.md +74 -36
- package/src/db/migrations/legacy-chain.json +197 -0
- package/src/db/migrations/meta/0000_snapshot.json +4182 -157
- package/src/db/migrations/meta/0002_snapshot.json +2478 -368
- package/src/db/migrations/meta/_journal.json +6 -279
- package/src/db/schema/agents.ts +57 -0
- package/src/db/schema/ai.ts +174 -0
- package/src/db/schema/auth.ts +183 -0
- package/src/db/schema/billing.ts +209 -0
- package/src/db/schema/feature-flags.ts +20 -0
- package/src/db/schema/identity.ts +250 -0
- package/src/db/schema/index.ts +12 -0
- package/src/db/schema/notifications.ts +31 -0
- package/src/db/schema/usage.ts +269 -0
- package/src/db/validate.ts +63 -0
- package/src/db/workspace-queries.ts +64 -0
- package/src/documents/classifier.ts +61 -0
- package/src/documents/errors.ts +44 -0
- package/src/documents/index.ts +24 -0
- package/src/documents/service.ts +256 -0
- package/src/documents/types.ts +24 -0
- package/src/email/brand.ts +24 -0
- package/src/email/index.ts +19 -0
- package/src/email/provider.ts +278 -0
- package/src/email/send.ts +169 -0
- package/src/email/senders.ts +292 -0
- package/src/email/templates/base-layout.tsx +145 -0
- package/src/email/templates/index.ts +28 -0
- package/src/email/templates/password-reset.tsx +72 -0
- package/src/email/templates/payment-failed.tsx +77 -0
- package/src/email/templates/quota-warning.tsx +118 -0
- package/src/email/templates/subscription-confirmed.tsx +91 -0
- package/src/email/templates/trial-expiry.tsx +124 -0
- package/src/email/templates/trial-warning.tsx +104 -0
- package/src/email/templates/verify-email.tsx +72 -0
- package/src/email/templates/welcome.tsx +64 -0
- package/src/email/templates/workspace-invitation.tsx +105 -0
- package/src/env.ts +180 -0
- package/src/identity/audit.ts +65 -0
- package/src/identity/errors.ts +44 -0
- package/src/identity/index.ts +26 -0
- package/src/identity/service.ts +202 -0
- package/src/identity/types.ts +28 -0
- package/src/index.ts +4 -0
- package/src/logger.ts +198 -0
- package/src/money.ts +270 -0
- package/src/notifications/index.ts +108 -0
- package/src/notifications/triggers.ts +158 -0
- package/src/notifications/types.ts +18 -0
- package/src/prompt.ts +142 -0
- package/src/registry.ts +75 -0
- package/src/request-context.ts +137 -0
- package/src/storage/index.ts +153 -0
- package/dist/db/schema/rag.d.ts +0 -171
- package/dist/db/schema/rag.d.ts.map +0 -1
- package/dist/db/schema/rag.js +0 -42
- package/dist/db/schema/rag.js.map +0 -1
- package/src/db/migrations/0000_sharp_night_nurse.sql +0 -52
- package/src/db/migrations/0001_hesitant_vapor.sql +0 -40
- package/src/db/migrations/0002_numerous_lady_vermin.sql +0 -235
- package/src/db/migrations/0003_mighty_jean_grey.sql +0 -37
- package/src/db/migrations/0004_cynical_grandmaster.sql +0 -2
- package/src/db/migrations/0005_careful_switch.sql +0 -8
- package/src/db/migrations/0006_tense_blue_marvel.sql +0 -16
- package/src/db/migrations/0007_next_squadron_sinister.sql +0 -1
- package/src/db/migrations/0008_powerful_scream.sql +0 -39
- package/src/db/migrations/0009_sad_ink.sql +0 -1
- package/src/db/migrations/0010_shocking_omega_flight.sql +0 -2
- package/src/db/migrations/0011_ambiguous_otto_octavius.sql +0 -151
- package/src/db/migrations/0011_hnsw_vector_index.sql +0 -17
- package/src/db/migrations/0012_user_profiles.sql +0 -30
- package/src/db/migrations/0013_trending_seasonal_columns.sql +0 -9
- package/src/db/migrations/0014_add_product_type_to_agents.sql +0 -6
- package/src/db/migrations/0015_new_v1_tables.sql +0 -149
- package/src/db/migrations/0016_document_types.sql +0 -30
- package/src/db/migrations/0017_document_metadata.sql +0 -2
- package/src/db/migrations/0018_identity_graph.sql +0 -200
- package/src/db/migrations/0019_db_driven_prompts.sql +0 -20
- package/src/db/migrations/0020_pending_extractions.sql +0 -35
- package/src/db/migrations/0021_rag_documents.sql +0 -39
- package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +0 -70
- package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +0 -17
- package/src/db/migrations/0024_user_memories_hnsw.sql +0 -23
- package/src/db/migrations/0025_audit_trigger_errcode.sql +0 -21
- package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +0 -17
- package/src/db/migrations/0027_billing_settings_mnt_columns.sql +0 -54
- package/src/db/migrations/0028_trial_credits_normalized_email.sql +0 -37
- package/src/db/migrations/0029_usage_records_conversation_idx.sql +0 -13
- package/src/db/migrations/0030_pending_extractions_composite_idx.sql +0 -11
- package/src/db/migrations/0031_rag_documents_hnsw.sql +0 -16
- package/src/db/migrations/0032_rate_limit_unique_idx.sql +0 -16
- package/src/db/migrations/0033_pending_extractions_partial_idx.sql +0 -18
- package/src/db/migrations/0034_rate_limit_count.sql +0 -9
- package/src/db/migrations/0035_credit_reservations.sql +0 -24
- package/src/db/migrations/0036_executions_audit_jobs.sql +0 -96
- package/src/db/migrations/0037_rate_limit_minute_bucket.sql +0 -46
- package/src/db/migrations/0038_user_quotas_generic_usage.sql +0 -25
- package/src/db/migrations/0039_platform_admin_impersonation.sql +0 -27
- package/src/db/migrations/0040_ee_audit_chain.sql +0 -23
- package/src/db/migrations/meta/0001_snapshot.json +0 -631
- package/src/db/migrations/meta/0003_snapshot.json +0 -2488
- package/src/db/migrations/meta/0004_snapshot.json +0 -2500
- package/src/db/migrations/meta/0005_snapshot.json +0 -2548
- package/src/db/migrations/meta/0006_snapshot.json +0 -2650
- package/src/db/migrations/meta/0007_snapshot.json +0 -2657
- package/src/db/migrations/meta/0008_snapshot.json +0 -2719
- package/src/db/migrations/meta/0009_snapshot.json +0 -2726
- package/src/db/migrations/meta/0010_snapshot.json +0 -2739
- package/src/db/migrations/meta/0011_snapshot.json +0 -3886
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { render } from "@react-email/components";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import { getEmailProvider, type EmailTemplateRef } from "./provider";
|
|
4
|
+
|
|
5
|
+
// Wraps the provider with the deployment's sender (EMAIL_FROM), React Email
|
|
6
|
+
// rendering, and up to 3 attempts with exponential backoff (1s, 2s, 4s) on
|
|
7
|
+
// 5xx, 429 and network errors. Never throws; returns a result object.
|
|
8
|
+
|
|
9
|
+
export interface SendEmailParams {
|
|
10
|
+
to: string | string[];
|
|
11
|
+
subject: string;
|
|
12
|
+
html: string;
|
|
13
|
+
/**
|
|
14
|
+
* Defaults to the EMAIL_FROM env var. There is no built-in sender: a
|
|
15
|
+
* provider that needs one refuses the send when neither is set.
|
|
16
|
+
*/
|
|
17
|
+
from?: string;
|
|
18
|
+
replyTo?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Provider-neutral template descriptor. Required by template-based
|
|
21
|
+
* providers (Loops); ignored by HTML-capable providers (Resend).
|
|
22
|
+
*/
|
|
23
|
+
template?: EmailTemplateRef;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SendEmailWithComponentParams {
|
|
27
|
+
to: string | string[];
|
|
28
|
+
subject: string;
|
|
29
|
+
/** A React Email component element. Will be rendered to HTML before sending. */
|
|
30
|
+
react: React.ReactElement;
|
|
31
|
+
/**
|
|
32
|
+
* Defaults to the EMAIL_FROM env var. There is no built-in sender: a
|
|
33
|
+
* provider that needs one refuses the send when neither is set.
|
|
34
|
+
*/
|
|
35
|
+
from?: string;
|
|
36
|
+
replyTo?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Provider-neutral template descriptor. Required by template-based
|
|
39
|
+
* providers (Loops); ignored by HTML-capable providers (Resend).
|
|
40
|
+
*/
|
|
41
|
+
template?: EmailTemplateRef;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SendEmailResult {
|
|
45
|
+
success: boolean;
|
|
46
|
+
id?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const MAX_ATTEMPTS = 3;
|
|
51
|
+
const BASE_DELAY_MS = 1000;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns true if the error is a client error (4xx) that should NOT be retried.
|
|
55
|
+
*/
|
|
56
|
+
function isClientError(error: unknown): boolean {
|
|
57
|
+
if (error && typeof error === "object") {
|
|
58
|
+
const err = error as Record<string, unknown>;
|
|
59
|
+
|
|
60
|
+
// 429 is excluded: rate limits are transient and worth retrying.
|
|
61
|
+
if (typeof err.statusCode === "number") {
|
|
62
|
+
return (
|
|
63
|
+
err.statusCode >= 400 && err.statusCode < 500 && err.statusCode !== 429
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof err.status === "number") {
|
|
68
|
+
return err.status >= 400 && err.status < 500 && err.status !== 429;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Resend SDK error names that indicate client errors
|
|
72
|
+
const name = err.name as string | undefined;
|
|
73
|
+
if (
|
|
74
|
+
name === "validation_error" ||
|
|
75
|
+
name === "missing_required_field" ||
|
|
76
|
+
name === "invalid_api_Key"
|
|
77
|
+
) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function delay(ms: number): Promise<void> {
|
|
86
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sends an email through the configured provider, retrying transient
|
|
91
|
+
* failures. Takes raw HTML via `{ html }` or a React Email element via
|
|
92
|
+
* `{ react }`. Never throws: failures return `{ success: false, error }`.
|
|
93
|
+
*/
|
|
94
|
+
export async function sendEmail(
|
|
95
|
+
params: SendEmailParams | SendEmailWithComponentParams
|
|
96
|
+
): Promise<SendEmailResult> {
|
|
97
|
+
try {
|
|
98
|
+
const provider = getEmailProvider();
|
|
99
|
+
const from = params.from ?? (process.env.EMAIL_FROM?.trim() || undefined);
|
|
100
|
+
|
|
101
|
+
const recipient = Array.isArray(params.to)
|
|
102
|
+
? params.to.join(", ")
|
|
103
|
+
: params.to;
|
|
104
|
+
|
|
105
|
+
let html: string;
|
|
106
|
+
if ("react" in params) {
|
|
107
|
+
try {
|
|
108
|
+
html = await render(params.react);
|
|
109
|
+
} catch (renderError) {
|
|
110
|
+
const message =
|
|
111
|
+
renderError instanceof Error
|
|
112
|
+
? renderError.message
|
|
113
|
+
: String(renderError);
|
|
114
|
+
console.error(`[Email] Failed to render React component: ${message}`);
|
|
115
|
+
return { success: false, error: `Template render failed: ${message}` };
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
html = params.html;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let lastError: unknown;
|
|
122
|
+
|
|
123
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
124
|
+
try {
|
|
125
|
+
const result = await provider.send({
|
|
126
|
+
to: params.to,
|
|
127
|
+
subject: params.subject,
|
|
128
|
+
html,
|
|
129
|
+
from,
|
|
130
|
+
replyTo: params.replyTo,
|
|
131
|
+
template: params.template,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return { success: true, id: result.id };
|
|
135
|
+
} catch (error) {
|
|
136
|
+
lastError = error;
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
|
|
139
|
+
if (isClientError(error)) {
|
|
140
|
+
console.error(
|
|
141
|
+
`[Email] Send failed for ${recipient} (client error, not retrying): ${message}`
|
|
142
|
+
);
|
|
143
|
+
return { success: false, error: message };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.warn(
|
|
147
|
+
`[Email] Send attempt ${attempt}/${MAX_ATTEMPTS} failed for ${recipient}: ${message}`
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
151
|
+
await delay(BASE_DELAY_MS * Math.pow(2, attempt - 1));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const finalMessage =
|
|
157
|
+
lastError instanceof Error ? lastError.message : String(lastError);
|
|
158
|
+
console.error(
|
|
159
|
+
`[Email] All ${MAX_ATTEMPTS} attempts failed for ${recipient}: ${params.subject}`
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return { success: false, error: finalMessage };
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// Catch-all for unexpected errors (e.g., getEmailProvider() failing)
|
|
165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
+
console.error(`[Email] Unexpected error: ${message}`);
|
|
167
|
+
return { success: false, error: message };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed wrappers that render a React Email template and send it. They never
|
|
3
|
+
* throw; each returns a SendEmailResult.
|
|
4
|
+
*
|
|
5
|
+
* Every sender attaches a stable `template` key and flat variables so
|
|
6
|
+
* template-based providers (Loops) can send the same email; HTML providers
|
|
7
|
+
* ignore it. Boolean variants use distinct keys (quota-warning /
|
|
8
|
+
* quota-exceeded, trial-warning / trial-depleted) because template providers
|
|
9
|
+
* branch per template, not per variable.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
import { emailBrand } from "./brand";
|
|
14
|
+
import { sendEmail, type SendEmailResult } from "./send";
|
|
15
|
+
import { formatMoney, type Money } from "../money";
|
|
16
|
+
import {
|
|
17
|
+
WelcomeEmail,
|
|
18
|
+
VerifyEmailEmail,
|
|
19
|
+
PasswordResetEmail,
|
|
20
|
+
WorkspaceInvitationEmail,
|
|
21
|
+
QuotaWarningEmail,
|
|
22
|
+
TrialWarningEmail,
|
|
23
|
+
TrialExpiryEmail,
|
|
24
|
+
PaymentFailedEmail,
|
|
25
|
+
SubscriptionConfirmedEmail,
|
|
26
|
+
} from "./templates";
|
|
27
|
+
|
|
28
|
+
/** The locale the email copy is written in; amounts are formatted to match. */
|
|
29
|
+
const EMAIL_LOCALE = "en";
|
|
30
|
+
|
|
31
|
+
export async function sendWelcomeEmail(params: {
|
|
32
|
+
to: string;
|
|
33
|
+
userName: string;
|
|
34
|
+
dashboardUrl: string;
|
|
35
|
+
}): Promise<SendEmailResult> {
|
|
36
|
+
return sendEmail({
|
|
37
|
+
to: params.to,
|
|
38
|
+
subject: emailBrand().name
|
|
39
|
+
? `Welcome to ${emailBrand().name}!`
|
|
40
|
+
: "Welcome!",
|
|
41
|
+
react: React.createElement(WelcomeEmail, {
|
|
42
|
+
userName: params.userName,
|
|
43
|
+
dashboardUrl: params.dashboardUrl,
|
|
44
|
+
}),
|
|
45
|
+
template: {
|
|
46
|
+
key: "welcome",
|
|
47
|
+
variables: {
|
|
48
|
+
userName: params.userName,
|
|
49
|
+
dashboardUrl: params.dashboardUrl,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function sendVerifyEmail(params: {
|
|
56
|
+
to: string;
|
|
57
|
+
userName: string;
|
|
58
|
+
verificationUrl: string;
|
|
59
|
+
}): Promise<SendEmailResult> {
|
|
60
|
+
return sendEmail({
|
|
61
|
+
to: params.to,
|
|
62
|
+
subject: "Verify your email address",
|
|
63
|
+
react: React.createElement(VerifyEmailEmail, {
|
|
64
|
+
userName: params.userName,
|
|
65
|
+
verificationUrl: params.verificationUrl,
|
|
66
|
+
}),
|
|
67
|
+
template: {
|
|
68
|
+
key: "verify-email",
|
|
69
|
+
variables: {
|
|
70
|
+
userName: params.userName,
|
|
71
|
+
verificationUrl: params.verificationUrl,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function sendPasswordResetEmail(params: {
|
|
78
|
+
to: string;
|
|
79
|
+
userName: string;
|
|
80
|
+
resetUrl: string;
|
|
81
|
+
}): Promise<SendEmailResult> {
|
|
82
|
+
return sendEmail({
|
|
83
|
+
to: params.to,
|
|
84
|
+
subject: "Reset your password",
|
|
85
|
+
react: React.createElement(PasswordResetEmail, {
|
|
86
|
+
userName: params.userName,
|
|
87
|
+
resetUrl: params.resetUrl,
|
|
88
|
+
}),
|
|
89
|
+
template: {
|
|
90
|
+
key: "password-reset",
|
|
91
|
+
variables: {
|
|
92
|
+
userName: params.userName,
|
|
93
|
+
resetUrl: params.resetUrl,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function sendInvitationEmail(params: {
|
|
100
|
+
to: string;
|
|
101
|
+
inviterName: string;
|
|
102
|
+
workspaceName: string;
|
|
103
|
+
role: string;
|
|
104
|
+
acceptUrl: string;
|
|
105
|
+
declineUrl: string;
|
|
106
|
+
}): Promise<SendEmailResult> {
|
|
107
|
+
return sendEmail({
|
|
108
|
+
to: params.to,
|
|
109
|
+
subject: `${params.inviterName} invited you to ${params.workspaceName}`,
|
|
110
|
+
react: React.createElement(WorkspaceInvitationEmail, {
|
|
111
|
+
inviterName: params.inviterName,
|
|
112
|
+
workspaceName: params.workspaceName,
|
|
113
|
+
role: params.role,
|
|
114
|
+
acceptUrl: params.acceptUrl,
|
|
115
|
+
declineUrl: params.declineUrl,
|
|
116
|
+
}),
|
|
117
|
+
template: {
|
|
118
|
+
key: "workspace-invitation",
|
|
119
|
+
variables: {
|
|
120
|
+
inviterName: params.inviterName,
|
|
121
|
+
workspaceName: params.workspaceName,
|
|
122
|
+
role: params.role,
|
|
123
|
+
acceptUrl: params.acceptUrl,
|
|
124
|
+
declineUrl: params.declineUrl,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Send the allowance warning or used-up email. `used` and `allowance`
|
|
132
|
+
* are money in the deployment's billing currency; the template and the
|
|
133
|
+
* provider variables receive them formatted.
|
|
134
|
+
*/
|
|
135
|
+
export async function sendQuotaWarningEmail(params: {
|
|
136
|
+
to: string;
|
|
137
|
+
workspaceName: string;
|
|
138
|
+
percentageUsed: number;
|
|
139
|
+
used: Money;
|
|
140
|
+
allowance: Money;
|
|
141
|
+
upgradeUrl: string;
|
|
142
|
+
isExceeded: boolean;
|
|
143
|
+
}): Promise<SendEmailResult> {
|
|
144
|
+
const used = formatMoney(params.used, EMAIL_LOCALE);
|
|
145
|
+
const allowance = formatMoney(params.allowance, EMAIL_LOCALE);
|
|
146
|
+
|
|
147
|
+
return sendEmail({
|
|
148
|
+
to: params.to,
|
|
149
|
+
subject: params.isExceeded
|
|
150
|
+
? "Monthly allowance used up"
|
|
151
|
+
: `Monthly allowance at ${params.percentageUsed}%`,
|
|
152
|
+
react: React.createElement(QuotaWarningEmail, {
|
|
153
|
+
workspaceName: params.workspaceName,
|
|
154
|
+
percentageUsed: params.percentageUsed,
|
|
155
|
+
used,
|
|
156
|
+
allowance,
|
|
157
|
+
upgradeUrl: params.upgradeUrl,
|
|
158
|
+
isExceeded: params.isExceeded,
|
|
159
|
+
}),
|
|
160
|
+
template: {
|
|
161
|
+
key: params.isExceeded ? "quota-exceeded" : "quota-warning",
|
|
162
|
+
variables: {
|
|
163
|
+
workspaceName: params.workspaceName,
|
|
164
|
+
percentageUsed: params.percentageUsed,
|
|
165
|
+
used,
|
|
166
|
+
allowance,
|
|
167
|
+
currency: params.allowance.currency,
|
|
168
|
+
upgradeUrl: params.upgradeUrl,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Send trial warning or depleted email. */
|
|
175
|
+
export async function sendTrialWarningEmail(params: {
|
|
176
|
+
to: string;
|
|
177
|
+
workspaceName: string;
|
|
178
|
+
creditsRemaining: number;
|
|
179
|
+
totalCredits: number;
|
|
180
|
+
percentageRemaining: number;
|
|
181
|
+
upgradeUrl: string;
|
|
182
|
+
isDepleted: boolean;
|
|
183
|
+
}): Promise<SendEmailResult> {
|
|
184
|
+
return sendEmail({
|
|
185
|
+
to: params.to,
|
|
186
|
+
subject: params.isDepleted
|
|
187
|
+
? "Trial credits depleted"
|
|
188
|
+
: "Trial credits running low",
|
|
189
|
+
react: React.createElement(TrialWarningEmail, {
|
|
190
|
+
workspaceName: params.workspaceName,
|
|
191
|
+
creditsRemaining: params.creditsRemaining,
|
|
192
|
+
totalCredits: params.totalCredits,
|
|
193
|
+
percentageRemaining: params.percentageRemaining,
|
|
194
|
+
upgradeUrl: params.upgradeUrl,
|
|
195
|
+
isDepleted: params.isDepleted,
|
|
196
|
+
}),
|
|
197
|
+
template: {
|
|
198
|
+
key: params.isDepleted ? "trial-depleted" : "trial-warning",
|
|
199
|
+
variables: {
|
|
200
|
+
workspaceName: params.workspaceName,
|
|
201
|
+
creditsRemaining: params.creditsRemaining,
|
|
202
|
+
totalCredits: params.totalCredits,
|
|
203
|
+
percentageRemaining: params.percentageRemaining,
|
|
204
|
+
upgradeUrl: params.upgradeUrl,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function sendPaymentFailedEmail(params: {
|
|
211
|
+
to: string;
|
|
212
|
+
workspaceName: string;
|
|
213
|
+
amount: string;
|
|
214
|
+
updatePaymentUrl: string;
|
|
215
|
+
}): Promise<SendEmailResult> {
|
|
216
|
+
return sendEmail({
|
|
217
|
+
to: params.to,
|
|
218
|
+
subject: `Payment failed for ${params.workspaceName}`,
|
|
219
|
+
react: React.createElement(PaymentFailedEmail, {
|
|
220
|
+
workspaceName: params.workspaceName,
|
|
221
|
+
amount: params.amount,
|
|
222
|
+
updatePaymentUrl: params.updatePaymentUrl,
|
|
223
|
+
}),
|
|
224
|
+
template: {
|
|
225
|
+
key: "payment-failed",
|
|
226
|
+
variables: {
|
|
227
|
+
workspaceName: params.workspaceName,
|
|
228
|
+
amount: params.amount,
|
|
229
|
+
updatePaymentUrl: params.updatePaymentUrl,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function sendSubscriptionConfirmedEmail(params: {
|
|
236
|
+
to: string;
|
|
237
|
+
workspaceName: string;
|
|
238
|
+
planName: string;
|
|
239
|
+
amount: string;
|
|
240
|
+
dashboardUrl: string;
|
|
241
|
+
}): Promise<SendEmailResult> {
|
|
242
|
+
return sendEmail({
|
|
243
|
+
to: params.to,
|
|
244
|
+
subject: `Subscription confirmed - ${params.planName} plan`,
|
|
245
|
+
react: React.createElement(SubscriptionConfirmedEmail, {
|
|
246
|
+
workspaceName: params.workspaceName,
|
|
247
|
+
planName: params.planName,
|
|
248
|
+
amount: params.amount,
|
|
249
|
+
dashboardUrl: params.dashboardUrl,
|
|
250
|
+
}),
|
|
251
|
+
template: {
|
|
252
|
+
key: "subscription-confirmed",
|
|
253
|
+
variables: {
|
|
254
|
+
workspaceName: params.workspaceName,
|
|
255
|
+
planName: params.planName,
|
|
256
|
+
amount: params.amount,
|
|
257
|
+
dashboardUrl: params.dashboardUrl,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function sendTrialExpiryEmail(params: {
|
|
264
|
+
to: string;
|
|
265
|
+
workspaceName: string;
|
|
266
|
+
expiryDate: string;
|
|
267
|
+
daysRemaining: number;
|
|
268
|
+
upgradeUrl: string;
|
|
269
|
+
}): Promise<SendEmailResult> {
|
|
270
|
+
return sendEmail({
|
|
271
|
+
to: params.to,
|
|
272
|
+
subject:
|
|
273
|
+
params.daysRemaining <= 0
|
|
274
|
+
? `Your trial for ${params.workspaceName} has ended`
|
|
275
|
+
: `Your trial ends in ${params.daysRemaining} day${params.daysRemaining === 1 ? "" : "s"}`,
|
|
276
|
+
react: React.createElement(TrialExpiryEmail, {
|
|
277
|
+
workspaceName: params.workspaceName,
|
|
278
|
+
expiryDate: params.expiryDate,
|
|
279
|
+
daysRemaining: params.daysRemaining,
|
|
280
|
+
upgradeUrl: params.upgradeUrl,
|
|
281
|
+
}),
|
|
282
|
+
template: {
|
|
283
|
+
key: "trial-expiry",
|
|
284
|
+
variables: {
|
|
285
|
+
workspaceName: params.workspaceName,
|
|
286
|
+
expiryDate: params.expiryDate,
|
|
287
|
+
daysRemaining: params.daysRemaining,
|
|
288
|
+
upgradeUrl: params.upgradeUrl,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Container,
|
|
4
|
+
Head,
|
|
5
|
+
Hr,
|
|
6
|
+
Html,
|
|
7
|
+
Link,
|
|
8
|
+
Preview,
|
|
9
|
+
Section,
|
|
10
|
+
Text,
|
|
11
|
+
} from "@react-email/components";
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
|
|
14
|
+
import { emailBrand } from "../brand";
|
|
15
|
+
|
|
16
|
+
// Inline styles only: email clients do not reliably support CSS classes.
|
|
17
|
+
// Colors are literal values, not CSS variables, so they survive dark-mode
|
|
18
|
+
// email clients.
|
|
19
|
+
|
|
20
|
+
export interface BaseLayoutProps {
|
|
21
|
+
preview: string;
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const colors = {
|
|
26
|
+
background: "#f4f4f5", // zinc-100
|
|
27
|
+
container: "#ffffff",
|
|
28
|
+
text: "#18181b", // zinc-900
|
|
29
|
+
muted: "#71717a", // zinc-500
|
|
30
|
+
brand: "#7c3aed", // violet-600
|
|
31
|
+
border: "#e4e4e7", // zinc-200
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
export function BaseLayout({ preview, children }: BaseLayoutProps) {
|
|
35
|
+
const brand = emailBrand();
|
|
36
|
+
return (
|
|
37
|
+
<Html lang="en">
|
|
38
|
+
<Head>
|
|
39
|
+
<meta charSet="utf-8" />
|
|
40
|
+
</Head>
|
|
41
|
+
<Preview>{preview}</Preview>
|
|
42
|
+
<Body style={bodyStyle}>
|
|
43
|
+
<Container style={containerStyle}>
|
|
44
|
+
{brand.name ? (
|
|
45
|
+
<Section style={headerStyle}>
|
|
46
|
+
<Text style={logoStyle}>{brand.name}</Text>
|
|
47
|
+
</Section>
|
|
48
|
+
) : null}
|
|
49
|
+
|
|
50
|
+
<Hr style={hrStyle} />
|
|
51
|
+
|
|
52
|
+
<Section style={contentStyle}>{children}</Section>
|
|
53
|
+
|
|
54
|
+
<Hr style={hrStyle} />
|
|
55
|
+
|
|
56
|
+
<Section style={footerStyle}>
|
|
57
|
+
{brand.name ? (
|
|
58
|
+
<Text style={footerTextStyle}>{brand.name}</Text>
|
|
59
|
+
) : null}
|
|
60
|
+
<Text style={footerMutedStyle}>
|
|
61
|
+
This is a transactional email
|
|
62
|
+
{brand.name ? ` from ${brand.name}` : ""}.
|
|
63
|
+
{brand.supportEmail ? (
|
|
64
|
+
<>
|
|
65
|
+
{" "}
|
|
66
|
+
If you believe you received this email in error, please
|
|
67
|
+
contact{" "}
|
|
68
|
+
<Link
|
|
69
|
+
href={`mailto:${brand.supportEmail}`}
|
|
70
|
+
style={footerLinkStyle}
|
|
71
|
+
>
|
|
72
|
+
{brand.supportEmail}
|
|
73
|
+
</Link>
|
|
74
|
+
.
|
|
75
|
+
</>
|
|
76
|
+
) : null}
|
|
77
|
+
</Text>
|
|
78
|
+
</Section>
|
|
79
|
+
</Container>
|
|
80
|
+
</Body>
|
|
81
|
+
</Html>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const bodyStyle: React.CSSProperties = {
|
|
86
|
+
backgroundColor: colors.background,
|
|
87
|
+
fontFamily:
|
|
88
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
89
|
+
margin: 0,
|
|
90
|
+
padding: "40px 0",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const containerStyle: React.CSSProperties = {
|
|
94
|
+
backgroundColor: colors.container,
|
|
95
|
+
borderRadius: "8px",
|
|
96
|
+
maxWidth: "600px",
|
|
97
|
+
margin: "0 auto",
|
|
98
|
+
border: `1px solid ${colors.border}`,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const headerStyle: React.CSSProperties = {
|
|
102
|
+
padding: "32px 40px 16px",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const logoStyle: React.CSSProperties = {
|
|
106
|
+
color: colors.brand,
|
|
107
|
+
fontSize: "24px",
|
|
108
|
+
fontWeight: 700,
|
|
109
|
+
margin: 0,
|
|
110
|
+
lineHeight: "32px",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const hrStyle: React.CSSProperties = {
|
|
114
|
+
borderColor: colors.border,
|
|
115
|
+
borderTop: "none",
|
|
116
|
+
margin: 0,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const contentStyle: React.CSSProperties = {
|
|
120
|
+
padding: "24px 40px",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const footerStyle: React.CSSProperties = {
|
|
124
|
+
padding: "16px 40px 32px",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const footerTextStyle: React.CSSProperties = {
|
|
128
|
+
color: colors.muted,
|
|
129
|
+
fontSize: "13px",
|
|
130
|
+
lineHeight: "20px",
|
|
131
|
+
margin: "0 0 8px",
|
|
132
|
+
fontWeight: 600,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const footerMutedStyle: React.CSSProperties = {
|
|
136
|
+
color: colors.muted,
|
|
137
|
+
fontSize: "12px",
|
|
138
|
+
lineHeight: "18px",
|
|
139
|
+
margin: 0,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const footerLinkStyle: React.CSSProperties = {
|
|
143
|
+
color: colors.brand,
|
|
144
|
+
textDecoration: "underline",
|
|
145
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { BaseLayout, type BaseLayoutProps } from "./base-layout";
|
|
2
|
+
export { WelcomeEmail, type WelcomeEmailProps } from "./welcome";
|
|
3
|
+
export { VerifyEmailEmail, type VerifyEmailEmailProps } from "./verify-email";
|
|
4
|
+
export {
|
|
5
|
+
PasswordResetEmail,
|
|
6
|
+
type PasswordResetEmailProps,
|
|
7
|
+
} from "./password-reset";
|
|
8
|
+
export {
|
|
9
|
+
WorkspaceInvitationEmail,
|
|
10
|
+
type WorkspaceInvitationEmailProps,
|
|
11
|
+
} from "./workspace-invitation";
|
|
12
|
+
export {
|
|
13
|
+
QuotaWarningEmail,
|
|
14
|
+
type QuotaWarningEmailProps,
|
|
15
|
+
} from "./quota-warning";
|
|
16
|
+
export {
|
|
17
|
+
TrialWarningEmail,
|
|
18
|
+
type TrialWarningEmailProps,
|
|
19
|
+
} from "./trial-warning";
|
|
20
|
+
export {
|
|
21
|
+
PaymentFailedEmail,
|
|
22
|
+
type PaymentFailedEmailProps,
|
|
23
|
+
} from "./payment-failed";
|
|
24
|
+
export {
|
|
25
|
+
SubscriptionConfirmedEmail,
|
|
26
|
+
type SubscriptionConfirmedEmailProps,
|
|
27
|
+
} from "./subscription-confirmed";
|
|
28
|
+
export { TrialExpiryEmail, type TrialExpiryEmailProps } from "./trial-expiry";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Button, Text } from "@react-email/components";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { BaseLayout } from "./base-layout";
|
|
4
|
+
|
|
5
|
+
export interface PasswordResetEmailProps {
|
|
6
|
+
userName: string;
|
|
7
|
+
resetUrl: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function PasswordResetEmail({
|
|
11
|
+
userName,
|
|
12
|
+
resetUrl,
|
|
13
|
+
}: PasswordResetEmailProps) {
|
|
14
|
+
const text = {
|
|
15
|
+
preview: "Reset your password",
|
|
16
|
+
heading: "Reset your password",
|
|
17
|
+
greeting: "Hi",
|
|
18
|
+
body: "We received a request to reset your password. Click the button below to choose a new one.",
|
|
19
|
+
button: "Reset Password",
|
|
20
|
+
note: "This link expires in 1 hour. If you didn't request a password reset, you can safely ignore this email.",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<BaseLayout preview={text.preview}>
|
|
25
|
+
<Text style={headingStyle}>{text.heading}</Text>
|
|
26
|
+
<Text style={bodyTextStyle}>
|
|
27
|
+
{text.greeting} {userName},
|
|
28
|
+
</Text>
|
|
29
|
+
<Text style={bodyTextStyle}>{text.body}</Text>
|
|
30
|
+
<Button href={resetUrl} style={buttonStyle}>
|
|
31
|
+
{text.button}
|
|
32
|
+
</Button>
|
|
33
|
+
<Text style={noteStyle}>{text.note}</Text>
|
|
34
|
+
</BaseLayout>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const headingStyle: React.CSSProperties = {
|
|
39
|
+
color: "#18181b",
|
|
40
|
+
fontSize: "22px",
|
|
41
|
+
fontWeight: 700,
|
|
42
|
+
lineHeight: "30px",
|
|
43
|
+
margin: "0 0 16px",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const bodyTextStyle: React.CSSProperties = {
|
|
47
|
+
color: "#18181b",
|
|
48
|
+
fontSize: "15px",
|
|
49
|
+
lineHeight: "24px",
|
|
50
|
+
margin: "0 0 12px",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const buttonStyle: React.CSSProperties = {
|
|
54
|
+
backgroundColor: "#7c3aed",
|
|
55
|
+
borderRadius: "6px",
|
|
56
|
+
color: "#ffffff",
|
|
57
|
+
display: "inline-block",
|
|
58
|
+
fontSize: "15px",
|
|
59
|
+
fontWeight: 600,
|
|
60
|
+
lineHeight: "100%",
|
|
61
|
+
padding: "12px 24px",
|
|
62
|
+
textDecoration: "none",
|
|
63
|
+
textAlign: "center" as const,
|
|
64
|
+
marginTop: "8px",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const noteStyle: React.CSSProperties = {
|
|
68
|
+
color: "#71717a",
|
|
69
|
+
fontSize: "13px",
|
|
70
|
+
lineHeight: "20px",
|
|
71
|
+
marginTop: "20px",
|
|
72
|
+
};
|