Skip to content

Email (@repo/email)

Transactional email package. Wraps Resend and React Email for type-safe, component-driven email rendering.

Location: packages/email/


sendEmail

ts
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_KEY is 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.

ts
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}
  />,
});
PropTypeDescription
userNamestringRecipient's display name
projectNamestringProject the photos belong to
photoCountnumberNumber of matched photos
photosUrlstringDeep link to the user's photo gallery

UserInvited

Invites an end user to join a project.

ts
import { UserInvited, userInvitedSubject } from '@repo/email';
PropTypeDescription
projectNamestringProject name shown in the email body
joinUrlstringRegistration link with embedded invite token

PhotographerInvited

Sent when a new photographer account is created — includes the account setup link.

ts
import { PhotographerInvited } from '@repo/email';
PropTypeDescription
photographerNamestringPhotographer's name
setupUrlstringOne-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.

ts
import { PhotographerAssigned, photographerAssignedSubject } from '@repo/email';
PropTypeDescription
photographerNamestringPhotographer's name
projectNamestringProject they were assigned to
projectUrlstringDeep link to the project in backoffice-web

PurchaseConfirmed

Receipt sent to an end user after a successful Stripe payment.

ts
import { PurchaseConfirmed, purchaseConfirmedSubject } from '@repo/email';
PropTypeDescription
userNamestringRecipient's display name
photoCountnumberNumber of photos purchased
amountFormattedstringHuman-readable amount e.g. "$9.99" or "Free"
downloadUrlstringLink to the purchases page

PasswordReset

Password reset link email. Also used for first-time password setup (when isFirstPassword is true).

ts
import { PasswordReset, passwordResetSubject } from '@repo/email';
PropTypeDescription
resetUrlstringPassword reset link (token embedded)
isFirstPasswordbooleanAdjusts copy for first-time setup vs. reset. Defaults to false.

Magic link sign-in email for backoffice users. Link expires in 15 minutes.

ts
import { MagicLink, magicLinkSubject } from '@repo/email';
PropTypeDescription
magicLinkUrlstringOne-time sign-in URL

SupportRequest

Internal notification email sent to the support inbox when an end user submits a support request.

ts
import { SupportRequest, supportRequestSubject } from '@repo/email';
PropTypeDescription
senderNamestringUser's display name
senderEmailstringUser's email address (used as reply-to)
categorystringSupport category chosen by the user
subjectstringUser-supplied subject line
messagestringFull message body

Environment variables

VariableRequiredDescription
RESEND_API_KEYNoResend API key. Email is silently skipped when unset.
RESEND_FROM_EMAILNoOverride 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

  1. Create packages/email/src/templates/MyTemplate.tsx — export the component and a subject helper
  2. Re-export both from packages/email/src/index.ts
  3. Create a BullMQ worker in apps/media-api/src/workers/ (or add to an existing service) that calls sendEmail with the new template
  4. Register the worker in apps/media-api/src/index.ts if it is a new worker file
  5. Update this doc

Wairo — Internal Documentation