# How to Add Email Integration to Your App — Copy-Paste Prompts Included

- Tool: App Features
- Last updated: July 2026

## TL;DR

Email integration needs an API provider (Resend is the best starting point — 3,000 free emails/month), an Edge Function that fires on database events or user actions, HTML email templates built with react-email, and an email_preferences table so users control which emails they receive. Lovable wires this in 4–6 hours. Running cost is $0–20/month for most early-stage apps — cost only scales with email volume.

## What Email Integration Actually Involves

Email integration is not just sending a message — it is a small system with four moving parts: a trigger (user signs up, order ships, a daily digest fires), an HTML template that renders your brand correctly in Gmail, Outlook, and Apple Mail, a delivery API that handles the SMTP plumbing and tracks bounces, and a preferences layer so users can unsubscribe from optional emails without losing critical transactional ones. The pieces that trip up first builds are DNS authentication (DKIM and SPF records), the difference between transactional and marketing email rules, and duplicate sends when Supabase Auth's built-in email fires alongside your custom Edge Function. Get those three right and email is a solved problem.

## Anatomy of the Feature

Seven components — two data tables, three backend functions, one UI layer, and one DNS service. AI tools build five of these reliably; DNS setup and delivery webhooks need careful attention.

- **Email Trigger** (backend): A Supabase Edge Function (Deno runtime) that fires on database events via Supabase Webhooks or pg_net, or on direct user actions (button click → Server Action → Edge Function). Imports the Resend SDK (@resend/node) or calls the SendGrid REST API with the recipient, template name, and variable payload.
- **HTML Email Template** (ui): React Email components (react-email, @react-email/components) for reusable, testable email layouts. Each template is a React component that accepts variables (userName, orderTotal, ctaUrl) and is rendered server-side to an HTML string via renderAsync(). react-email's preview server lets you see template changes in a browser before sending.
- **Email Preferences Table** (data): Supabase table email_preferences with boolean columns per email category: marketing, product_updates, transactional. The transactional flag should never disable truly critical emails (password reset, security alerts) — enforce this in the Edge Function logic, not the UI. Users manage their own row via a preferences page in the app.
- **Unsubscribe Handler** (backend): A Supabase Edge Function or Next.js API route that receives unsubscribe link clicks. The link contains a signed JWT (HMAC-SHA256) encoding the user_id and email_type — no login required to unsubscribe. The handler validates the JWT, flips the relevant boolean in email_preferences, and returns a confirmation page.
- **Delivery Webhook Receiver** (backend): An Edge Function endpoint registered with Resend or SendGrid to receive real-time delivery events: delivered, bounced, complained, opened. Inserts events into email_delivery_log. On hard bounce, sets a suppressed flag on the user record to prevent future sends. Alerts admin for unusual bounce spike.
- **Admin Email Log** (data): Supabase table email_delivery_log recording every email send attempt with its outcome: provider_message_id, status (sent, delivered, bounced, complained), delivered_at, opened_at. Drives an admin deliverability dashboard showing per-template delivery rates and flagging users with bounced addresses.
- **DKIM/SPF DNS Records** (service): TXT DNS records added to your sending domain in your domain registrar. DKIM (DomainKeys Identified Mail) signs each email with a cryptographic key that receiving mail servers verify against your DNS. SPF (Sender Policy Framework) lists the servers authorized to send mail from your domain. Both are required for reliable Gmail inbox delivery — without them, emails land in spam.

## Data model

Two tables: email_preferences for user opt-in/out control, and email_delivery_log for admin deliverability visibility. Run this in the Supabase SQL editor.

```sql
create table public.email_preferences (
  user_id uuid references auth.users on delete cascade primary key,
  marketing bool not null default true,
  product_updates bool not null default true,
  transactional bool not null default true,
  updated_at timestamptz not null default now()
);

alter table public.email_preferences enable row level security;

create policy "Users can view own email preferences"
  on public.email_preferences for select
  using (auth.uid() = user_id);

create policy "Users can upsert own email preferences"
  on public.email_preferences for insert
  with check (auth.uid() = user_id);

create policy "Users can update own email preferences"
  on public.email_preferences for update
  using (auth.uid() = user_id);

create table public.email_delivery_log (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users on delete set null,
  template_name text not null,
  provider_message_id text,
  status text not null default 'sent',
  error_message text,
  sent_at timestamptz not null default now(),
  delivered_at timestamptz,
  opened_at timestamptz
);

alter table public.email_delivery_log enable row level security;

create policy "Service role can insert delivery log"
  on public.email_delivery_log for insert
  to service_role
  with check (true);

create policy "Service role can update delivery log"
  on public.email_delivery_log for update
  to service_role
  using (true);

create index email_log_user_idx
  on public.email_delivery_log (user_id, sent_at desc);

create index email_log_status_idx
  on public.email_delivery_log (status, sent_at desc);
```

The service_role INSERT/UPDATE policy on email_delivery_log lets your Edge Functions (which use the service role key) write delivery events without exposing the log to regular users. Admins query it directly from the Supabase dashboard.

## Build paths

### Lovable — fit 4/10, 4–6 hours

Lovable's Supabase Edge Functions + Secrets panel makes Resend integration the smoothest path — store the API key in Cloud tab → Secrets and prompt for Edge Functions per email type.

1. In Lovable's Cloud tab, go to Secrets and add your RESEND_API_KEY — never paste it into the prompt or the code
2. Paste the prompt below to generate Edge Functions for each email type and the email_preferences table
3. Open the Resend dashboard, go to Domains, add your sending domain, and copy the DKIM TXT and SPF TXT records into your domain registrar DNS settings
4. Test each email type by triggering the action (sign up, place order) on the published Lovable URL and verifying delivery in the Resend Logs tab
5. Register a webhook endpoint in Resend dashboard pointing to your delivery-webhook Edge Function to start capturing bounce events

Starter prompt:

```
Add email integration using Resend as the provider. Requirements: (1) Create separate Supabase Edge Functions for each of these email types: send-welcome-email (fires when a new user row is inserted in auth.users via Supabase Webhook), send-password-reset (fires from a button action — Supabase Auth handles this natively so only build custom if overriding default), and send-weekly-digest (will be triggered manually for now). Each function imports @resend/node and reads RESEND_API_KEY from Deno.env.get('RESEND_API_KEY'). (2) Create react-email templates for the welcome email and weekly digest using @react-email/components — include the app logo (use placeholder URL), a heading, body text with user name variable, and a CTA button with brand color. (3) Create an email_preferences Supabase table (user_id, marketing bool default true, product_updates bool default true, transactional bool default true) with RLS so users manage only their own row. (4) Add an unsubscribe Edge Function that validates a signed JWT in the query string and flips the marketing boolean in email_preferences — no login required. (5) Add an email-delivery-webhook Edge Function that receives Resend delivery events and inserts into an email_delivery_log table (user_id, template_name, status, provider_message_id, sent_at). (6) Create a user-facing Email Preferences page where logged-in users toggle marketing and product_updates preferences. (7) Edge case: if status is 'bounced' in email_delivery_log, do not send future marketing emails to that address — check the log before sending in each function. Store the API key in Supabase Secrets, never in code.
```

Limitations:

- Lovable cannot preview react-email templates visually — use the Resend dashboard's preview URL or run react-email's local preview server separately
- DNS/DKIM setup is outside Lovable — done in your domain registrar's DNS panel; propagation takes up to 48 hours
- Lovable may generate duplicate email sends if Supabase Auth's built-in email confirmation is still enabled — disable it in Supabase dashboard under Authentication → Email Templates if managing emails yourself

### V0 — fit 3/10, 3–5 hours

V0 generates clean Next.js API routes for Resend sends — good when email is one feature among many in a Next.js app and you want typed server-side logic.

1. Paste the prompt below and V0 generates API routes in app/api/email/ for each email type
2. Open the Vars panel in V0 and add RESEND_API_KEY — or add it manually to Vercel Dashboard environment variables after pushing to GitHub
3. Run the SQL data model above in the Supabase SQL editor and add NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_KEY to Vercel env vars
4. Test email delivery by triggering each API route from the app UI on the deployed Vercel URL

Starter prompt:

```
Add email integration using Resend to this Next.js App Router project. Requirements: (1) Create server-side API routes in app/api/email/welcome/route.ts and app/api/email/digest/route.ts that accept POST with user data and send emails via the Resend SDK (@resend/node). Read RESEND_API_KEY from process.env. (2) Create react-email templates in emails/ directory for welcome and digest — use @react-email/components Html, Head, Body, Container, Heading, Text, Button components; accept userName, ctaUrl, unsubscribeUrl as props. (3) Create a Server Action in lib/actions/email.ts that checks the user's email_preferences row in Supabase before calling the email API route — skip send if preference is false. (4) Add an API route app/api/email/unsubscribe/route.ts that reads a JWT token from query params, verifies with UNSUBSCRIBE_SECRET from env, and flips the relevant preference in email_preferences via service role Supabase client. (5) Create a /settings/notifications page where authenticated users toggle email preference booleans — use Server Actions for updates. (6) Explicitly request the email_preferences table schema with RLS. Do not add RESEND_API_KEY or SUPABASE_SERVICE_KEY to any frontend file — server-side only.
```

Limitations:

- V0 does not auto-provision Resend or configure env vars — manual setup in Vercel Dashboard required
- V0 may skip the email_preferences table unless explicitly requested in the prompt — include it
- V0 generates frontend components cleanly but Supabase Server Actions for email log inserts need manual review to ensure they use the service role key, not the anon key

### Flutterflow — fit 2/10, 5–8 hours

FlutterFlow triggers email via Supabase Edge Functions or direct REST API calls — no native email widget, so all email logic lives in backend functions triggered from mobile actions.

1. Deploy the email Edge Functions from the Lovable path (or write them separately) to Supabase — FlutterFlow calls them via HTTP Action
2. In FlutterFlow's Action editor, add an API Call action that POSTs to your Edge Function URL with the user's data as JSON body
3. Store the Edge Function URL in FlutterFlow's App Constants — never hardcode it in individual actions
4. Add a Notifications preferences screen with toggle widgets that update email_preferences via a Supabase query action

Limitations:

- FlutterFlow has no email template preview — iterate on templates using Resend's dashboard outside FlutterFlow
- Mobile apps primarily send email via backend triggers (new booking, order update), not direct user composition — design for transactional use cases only
- DKIM and DNS setup is entirely outside FlutterFlow — done in your domain registrar and Resend dashboard

### Custom — fit 5/10, 3–5 days

Full custom build is the right choice when email is a core product feature — newsletter platforms, email clients, transactional systems requiring A/B testing, dedicated IPs, or multi-provider failover.

1. Build a template management system where HTML email templates are stored as database rows and editable without code deploys
2. Implement SendGrid Dynamic Templates for A/B testing subject lines and CTA copy with statistical significance tracking
3. Set up dedicated IP pools in SendGrid with a warm-up schedule for high-volume sending to protect sender reputation
4. Build a suppression list synced across providers so a hard bounce in Resend prevents SendGrid fallover from resending

Limitations:

- Dedicated IP warm-up (sending volume that ramps over 4–6 weeks) adds significant ops overhead before production sending begins
- Full deliverability setup (DKIM, SPF, DMARC, BIMI) plus reputation monitoring is a week of standalone work separate from feature development

## Gotchas

- **Emails go straight to Gmail spam** — DKIM and SPF DNS records are not configured for the sending domain. Emails arrive from Resend's shared IP pool without domain authentication — Gmail's spam filter flags them as potentially spoofed. This is the single most common email integration failure and it is invisible in development because you see 'sent' status in the API but recipients never see the message. Fix: Go to the Resend dashboard → Domains → Add Domain, then copy the two DKIM TXT records and the SPF TXT record shown there into your domain registrar's DNS settings. Wait up to 48 hours for propagation, then verify at MXToolbox (search your domain with the TXT check). All three records must show green before sending to real users.
- **Users receive two welcome emails after signup** — Supabase Auth has built-in email confirmation enabled by default — it sends a welcome/confirmation email immediately on signup. If your custom Edge Function also fires on the auth.users insert event, the user gets both emails within seconds. This burns sender reputation and confuses users. Fix: Decide which system owns email: either use Supabase Auth's built-in templates (configure them in Supabase dashboard → Authentication → Email Templates) or disable them and own all emails yourself via Edge Functions. To disable: Authentication → Email Templates → toggle off 'Confirm signup'. Do not run both.
- **Unsubscribe link returns 404 after deployment** — The unsubscribe Edge Function was generated but its URL was not set as an environment variable in the deployed app, or the function was not deployed to production (it exists locally but not on Supabase). The signed JWT in the unsubscribe URL contains an expiry, so old links also fail after the default 24-hour window. Fix: Verify the Edge Function is deployed: open Supabase dashboard → Edge Functions and confirm the function shows 'Active' status. Add UNSUBSCRIBE_SECRET to Supabase Secrets (Cloud tab in Lovable, or Supabase dashboard → Edge Functions → Secrets). Set JWT expiry to 30 days in the signing code so links in weekly digests remain valid.
- **Edge Function times out sending emails with large templates** — react-email's renderAsync() compiles large templates with many conditional sections on every invocation. In cold-start Edge Functions (which have no warm state), this compilation takes 8–15 seconds, exceeding the default 10-second Edge Function timeout and causing a 504 error. Fix: Pre-render templates at build time into static HTML strings stored in Supabase Storage as .html files. The Edge Function fetches the pre-rendered HTML and performs string replacement for variables ({{userName}}, {{ctaUrl}}) rather than re-rendering the React component. This reduces per-send execution time from 10+ seconds to under 1 second.

## Best practices

- Use Resend for new projects — the developer experience, react-email native support, and 3,000 free emails/month make it the default choice for apps under 50K monthly sends
- Never store RESEND_API_KEY or SendGrid keys in frontend code — always use Supabase Secrets (Edge Functions) or Vercel env vars (Next.js API routes), server-side only
- Set up DKIM and SPF DNS records before sending to any real user — spam folder delivery poisons your sender reputation in the first 24 hours
- Keep the transactional boolean in email_preferences always true for critical flows (password reset, security alerts) — only marketing and product_updates should be user-toggleable
- Add the unsubscribe URL with a signed JWT to every marketing and digest email footer — CAN-SPAM and GDPR both require one-click unsubscribe with no login barrier
- Cache the email_preferences lookup in your Edge Function using the session — don't query the table for every send in a batch digest run
- Log every send attempt to email_delivery_log before the API call, then update status on the webhook callback — this gives you a complete audit trail even if the API call times out
- Pre-render react-email templates to static HTML at build time and store in Supabase Storage — cold-start template compilation causes Edge Function timeouts at scale

## Frequently asked questions

### Which email provider is best for a new app — Resend or SendGrid?

Resend for most new projects. It has native react-email support, a cleaner API, and 3,000 free emails per month with no daily cap limits as restrictive as SendGrid's 100/day free tier. SendGrid makes more sense if you need Dynamic Templates for A/B testing subject lines, dedicated IP management for high-volume sending, or if your team already has a SendGrid account with sender reputation built up.

### Do I need to verify my domain to send emails?

Yes, for production. You can send from an unverified domain during development, but emails will land in spam for most recipients. Domain verification means adding two DKIM TXT records and one SPF TXT record to your domain's DNS — Resend's dashboard shows you exactly what to copy. Allow up to 48 hours for DNS propagation before testing deliverability.

### What is the difference between transactional and marketing emails?

Transactional emails are sent in direct response to a user action — welcome email after signup, password reset, order confirmation, security alert. Marketing emails are sent proactively — weekly digests, promotional campaigns, product announcements. CAN-SPAM and GDPR apply stricter rules to marketing emails (mandatory unsubscribe, no deceptive headers) while transactional emails have more flexibility. Never put a user into a 'no email' state that blocks their password reset.

### How do I stop emails going to spam?

Three steps cover 95% of spam filter failures: (1) Set up DKIM and SPF records for your sending domain — do this before your first production send. (2) Start with low volume and gradually increase to build sender reputation. (3) Maintain a clean list by suppressing hard bounces immediately and processing unsubscribes within 10 days as required by CAN-SPAM. Gmail's Postmaster Tools dashboard shows your domain reputation once volume picks up.

### Can users unsubscribe from specific email types?

Yes — the email_preferences table stores boolean flags per category (marketing, product_updates, transactional). Each category maps to a toggle in your app's notification settings page. The unsubscribe link in email footers should specify which category to unsubscribe from, not silently disable all email. Transactional should always remain true unless the user explicitly requests account deletion.

### How do I preview my email template before sending?

react-email includes a local preview server — run it in a separate browser tab outside your main app to see templates rendered with test data. The Resend dashboard also shows a preview of recent sends in the Logs tab. For cross-client testing (Gmail vs Outlook vs Apple Mail rendering), Litmus or Email on Acid provide screenshot previews across 90+ clients — useful before a major launch but not required for every iteration.

### What happens when an email bounces?

Hard bounces (permanent delivery failure — the address doesn't exist) should be recorded in email_delivery_log with status 'bounced' and suppressed from all future non-critical sends. Soft bounces (temporary failure — mailbox full) can be retried 2–3 times over 24 hours. Resend and SendGrid both send bounce events to your registered webhook endpoint automatically — your delivery webhook Edge Function updates the log.

### Is there a free tier that covers small apps?

Resend's free tier (3,000 emails/month, 100/day) covers welcome and transactional emails for apps under roughly 300 active users sending 10 emails each per month. The 100/day cap matters if you trigger batch sends — a welcome email campaign for 200 new users in a day would hit the cap. Upgrade to Resend Pro ($20/mo, approx) when you add weekly digests or the user base grows past 300.

---

Source: https://www.rapidevelopers.com/app-features/email-integration
© RapidDev — https://www.rapidevelopers.com/app-features/email-integration
