Email (@repo/email)
Transactional email package. Wraps Resend and React Email for type-safe, component-driven email rendering.
Location: packages/email/
sendEmail
import { sendEmail } from '@repo/email';
await sendEmail({
to: 'user@example.com',
subject: 'Your subject line',
react: <MyTemplate prop="value" />,
replyTo: 'support@wairo.app', // optional
});Behaviour:
- Renders the React component to HTML via
@react-email/render - Sends via the Resend API
- No-ops silently when
RESEND_API_KEYis not set — safe for local dev - Throws on Resend API errors so BullMQ can retry the calling worker job
From address: defaults to no-reply@wairo.app. Override with RESEND_FROM_EMAIL env var (e.g. onboarding@resend.dev for staging without a verified domain).
Templates
Each template exports a React component and a subject-line helper. Both are used together when calling sendEmail.
UserTagged
Notifies an end user that they appear in one or more photos.
import { UserTagged, userTaggedSubject } from '@repo/email';
await sendEmail({
to: user.email,
subject: userTaggedSubject(projectName, photoCount),
react: <UserTagged
userName={user.name}
projectName={projectName}
photoCount={photoCount}
photosUrl={photosUrl}
/>,
});| Prop | Type | Description |
|---|---|---|
userName | string | Recipient's display name |
projectName | string | Project the photos belong to |
photoCount | number | Number of matched photos |
photosUrl | string | Deep link to the user's photo gallery |
UserInvited
Invites an end user to join a project.
import { UserInvited, userInvitedSubject } from '@repo/email';| Prop | Type | Description |
|---|---|---|
projectName | string | Project name shown in the email body |
joinUrl | string | Registration link with embedded invite token |
PhotographerInvited
Sent when a new photographer account is created — includes the account setup link.
import { PhotographerInvited } from '@repo/email';| Prop | Type | Description |
|---|---|---|
photographerName | string | Photographer's name |
setupUrl | string | One-time account setup URL (setup token embedded) |
No subject helper — caller constructs the subject directly.
PhotographerAssigned
Notifies an existing photographer that they have been assigned to a new project.
import { PhotographerAssigned, photographerAssignedSubject } from '@repo/email';| Prop | Type | Description |
|---|---|---|
photographerName | string | Photographer's name |
projectName | string | Project they were assigned to |
projectUrl | string | Deep link to the project in backoffice-web |
PurchaseConfirmed
Receipt sent to an end user after a successful Stripe payment.
import { PurchaseConfirmed, purchaseConfirmedSubject } from '@repo/email';| Prop | Type | Description |
|---|---|---|
userName | string | Recipient's display name |
photoCount | number | Number of photos purchased |
amountFormatted | string | Human-readable amount e.g. "$9.99" or "Free" |
downloadUrl | string | Link to the purchases page |
PasswordReset
Password reset link email. Also used for first-time password setup (when isFirstPassword is true).
import { PasswordReset, passwordResetSubject } from '@repo/email';| Prop | Type | Description |
|---|---|---|
resetUrl | string | Password reset link (token embedded) |
isFirstPassword | boolean | Adjusts copy for first-time setup vs. reset. Defaults to false. |
MagicLink
Magic link sign-in email for backoffice users. Link expires in 15 minutes.
import { MagicLink, magicLinkSubject } from '@repo/email';| Prop | Type | Description |
|---|---|---|
magicLinkUrl | string | One-time sign-in URL |
SupportRequest
Internal notification email sent to the support inbox when an end user submits a support request.
import { SupportRequest, supportRequestSubject } from '@repo/email';| Prop | Type | Description |
|---|---|---|
senderName | string | User's display name |
senderEmail | string | User's email address (used as reply-to) |
category | string | Support category chosen by the user |
subject | string | User-supplied subject line |
message | string | Full message body |
Environment variables
| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY | No | Resend API key. Email is silently skipped when unset. |
RESEND_FROM_EMAIL | No | Override the sender address. Useful for staging (onboarding@resend.dev). |
RESEND_FROM_EMAIL is read directly from process.env in packages/email/src/send.ts because it is purely a deployment-time override and does not need Zod validation.
Adding a new template
- Create
packages/email/src/templates/MyTemplate.tsx— export the component and a subject helper - Re-export both from
packages/email/src/index.ts - Create a BullMQ worker in
apps/media-api/src/workers/(or add to an existing service) that callssendEmailwith the new template - Register the worker in
apps/media-api/src/index.tsif it is a new worker file - Update this doc