Skip to content

Implement email on a client site

Purpose

Explain how a portfolio (client) site uses the MKP Works email system — what you configure on the platform, what you install on the client, which APIs to call, and how to hand the work to another developer or LLM with a self-contained brief.

What this system is (and is not)

MKP Works owns outbound, template-based transactional email. You do not run SMTP, SES, SendGrid, Resend, Nodemailer, or any mail SDK on the client site for this flow. The platform renders Django email templates, delivers mail, and writes an audit row (EmailDelivery).

Supported Not supported
Confirmation / notification mail after a form submit Inbound mail / reading a mailbox
Explicit templated sends from your trusted backend Arbitrary “send any email to anyone” from the browser
Templates and recipients configured in platform admin Client sites defining templates in their own code

There are two client integration paths. Pick one (or both):

  1. Forms path (browser) — User submits a form; MKP sends confirmation + staff notification automatically.
  2. Server notifications path (backend) — Your server calls MKP to send a named template.

Browser clients cannot send arbitrary mail. Never put server secrets in frontend code.

Live prototype

End-to-end form → email on this platform (docs ClientSite mkpworks-docs):

Working email-sending form

Submit with your email. Confirmation + staff notification are sent by the email gateway. Check admin Email deliveries.

Code to paste on your client site

<!-- Loads when the live demo config is available -->

Server-notification live control (docs proxy; production clients use server secrets on the backend only):

Server notification demo


Prerequisites (platform admin)

Do this once in Django Admin (/admin/) before coding the client site.

  1. ClientSite exists, status active, with your product domain(s) on AllowedDomain.
  2. Credentials
  3. Public site key (pk_...) — browser / forms only.
  4. Server credentialX-MKP-Client-Id (skpub_...) + X-MKP-Client-Secret (sk_...) for trusted backends. The raw secret is shown once when issued.
  5. support_email on the ClientSite — required for form staff notifications and for templates that use recipient mode site_support_address.
  6. EmailTemplate rows for your site (or global templates), each with a unique code (for example forms.submission_received, forms.staff_notification, orders.shipped).
  7. For the forms path: a FormDefinition with fields, linked to confirmation and/or notification templates.

Recipient modes (important for server sends)

When your backend calls the notifications API, the template’s allowed_recipient_mode can override the recipient you pass:

Mode Effective recipient
submitted_email The recipient you provide
site_support_address Forced to ClientSite.support_email
configured_address Forced to the template’s configured_recipient
authenticated_user Forced to the authenticated user’s email (user required)
staff_only Requires a staff user; otherwise denied

The forms path uses an internal force-recipient flag, so confirmation goes to the submitter and staff mail goes to support_email even when modes would otherwise rewrite recipients.

Template variables

Templates use Django template language. Forms pass context like:

  • {{ form.name }}, {{ form.slug }}
  • {{ payload.email }}, {{ payload.name }}, … (submission fields)
  • {{ submission }} (submission object)

For server notifications, you supply context yourself; reference those keys in the subject/body templates (for example {{ order_id }}, {{ payload.name }}).


Path A — Forms email (browser / JavaScript)

Use this when contact, waitlist, or lead forms should trigger confirmation and/or staff notification without your backend sending mail.

What to install on the client site

Item Required? Notes
Hosted script mkp.js Yes (easiest) No npm package. Zero JS dependencies.
npm / Node mail libraries No Do not install Nodemailer, Resend, etc. for this
Server secret No Use site key only in the browser

Hosted script URL (production example):

https://mkpworks.com/sdk/v1/mkp.js

Local / custom base URL: {PLATFORM_BASE_URL}/sdk/v1/mkp.js.

Embed example

<script src="https://mkpworks.com/sdk/v1/mkp.js"></script>
<script>
  MKP.init({
    baseUrl: "https://mkpworks.com",
    siteKey: "pk_YOUR_SITE_KEY"
  });
</script>
<div data-mkp="form" data-form="contact"></div>

Replace contact with your form slug. The page origin must match an allowed domain for the site.

API endpoints (forms)

Base: {PLATFORM}/api/v1/

Get form schema

GET /api/v1/forms/{form_slug}/
X-MKP-Site-Key: pk_...
Accept: application/json

Submit (triggers emails)

POST /api/v1/forms/{form_slug}/submissions/
X-MKP-Site-Key: pk_...
Origin: https://your-client-domain.example
Content-Type: application/json
{
  "data": {
    "name": "Ada",
    "email": "ada@example.com",
    "message": "Hello",
    "_hp": "",
    "_t": "1710000000.0"
  },
  "source_url": "https://your-client-domain.example/contact"
}

Flat body is also accepted (email, message, source_url at the top level). Honeypot _hp should stay empty; _t is a timing token used by spam controls.

Success: 201 with submission fields (id, status, email, created_at, spam_score, …).

Email side effects (only when status is received and an email is present; skipped for spam/quarantine):

  1. If the form has a notification template and the site has support_email → staff notification.
  2. If the form has a confirmation template → confirmation to the submitter.

There is no browser API to send mail directly.


Path B — Server notifications (trusted backend)

Use this when your product backend should send a named template (order updates, account events, custom product mail) after some server-side event.

What to install on the client site

Stack Install
Python Prefer the platform SDK at sdk/python/mkp_client (add to PYTHONPATH or install as a local package). Runtime dependency: httpx.
Any language Nothing required beyond an HTTP client. Call the REST endpoint with the headers below.
Browser / frontend Do not call this endpoint. Do not ship sk_... secrets to the client.
Mail SDKs (SendGrid, etc.) Not used for MKP transactional mail

API endpoint

POST /api/v1/server/notifications/
X-MKP-Client-Id: skpub_...
X-MKP-Client-Secret: sk_...
Content-Type: application/json
Accept: application/json
{
  "template": "forms.submission_received",
  "recipient": "user@example.com",
  "context": {
    "payload": { "name": "Ada" },
    "order_id": "ord_123"
  }
}

Response 200:

{
  "status": "sent",
  "recipient": "u***@example.com"
}

status may be sent or failed. The recipient in the response is masked for privacy. Failed deliveries are audited on the platform; treat non-sent as a soft failure in your product logic.

Python SDK example

from mkp_client import MKPClient

client = MKPClient(
    base_url="https://mkpworks.com",
    client_id="skpub_...",
    client_secret="sk_...",
)
result = client.notifications.send(
    "forms.submission_received",
    "user@example.com",
    context={"payload": {"name": "Ada"}},
)
# result -> {"status": "sent"|"failed", "recipient": "u***@..."}

Raw HTTP example (any language)

curl -sS -X POST "https://mkpworks.com/api/v1/server/notifications/" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "X-MKP-Client-Id: skpub_..." \
  -H "X-MKP-Client-Secret: sk_..." \
  -d '{
    "template": "forms.submission_received",
    "recipient": "user@example.com",
    "context": {"payload": {"name": "Ada"}}
  }'

Replit development mode (saved Repl IDs)

Use this when the client site runs on Replit and you want browser forms to call this platform without adding every preview URL as an Allowed Domain, and without opening all of Replit.

Production ignores saved Repl IDs. Only the development settings module sets MKP_ALLOW_REPLIT_DEV_IDS=True.

What you do (step by step)

  1. Run this platform in development on Replit (DJANGO_SETTINGS_MODULE=mkpworks.settings.development). Confirm the platform preview URL loads (that URL is your {PLATFORM_BASE_URL}).
  2. On the client Repl, get its ID in the Shell:
    echo $REPL_ID
    
    Copy the value (example: abc123def456).
  3. In platform admin → Client sites → your site → Allowed domains → add the current Replit preview hostname (from the browser address bar).
  4. One ID per line, or comma-separated.
  5. Add every client Repl you will test from (only your IDs).
  6. Issue a public site key for that ClientSite if you do not already have one (pk_...).
  7. On the client site, point mkp.js at the platform Repl root (not a subdomain):
    <script src="{PLATFORM_BASE_URL}/sdk/v1/mkp.js"></script>
    <script>
      MKP.init({
        baseUrl: "{PLATFORM_BASE_URL}",
        siteKey: "pk_YOUR_SITE_KEY"
      });
    </script>
    <div data-mkp="form" data-form="contact"></div>
    
    Replace {PLATFORM_BASE_URL} with this platform’s Replit HTTPS origin (for example https://your-platform-host.replit.dev).
  8. Open the client Repl preview and submit the form. A matching saved Repl ID on a *.replit.dev / *.replit.app / *.repl.co Origin is enough for Origin checks in development.
  9. Confirm mail side effects in platform admin (Email deliveries). In development, message bodies usually print to the platform console (EMAIL_BACKEND console).

What this does / does not do

Layer Behavior in Replit dev mode
CORS (development) Allows browser calls from Replit preview host patterns so preflight works
Form Origin check Allows only Origins whose hostname is a Replit preview host and contains one of this site’s saved Repl IDs
Other people’s Repls Rejected (their REPL_ID is not on your ClientSite)
Production MKP_ALLOW_REPLIT_DEV_IDS=False; use exact Allowed Domains only
Server notifications Unaffected — use server secrets from your backend; no browser Origin gate

Optional: exact domain instead of Repl ID

You can still add the client preview hostname under Allowed Domains (exact host). Saved Repl IDs are the less brittle option when preview URLs change but REPL_ID stays stable.


Checklist for a new client site

  1. Register ClientSite + allowed domain(s) + support_email.
  2. For Replit client testing: add saved Replit ids on the ClientSite (see above).
  3. Issue site key and/or server secret.
  4. Create EmailTemplate(s) with stable code values and correct recipient modes.
  5. Forms path: create FormDefinition, wire confirmation/notification templates, embed mkp.js (or call form APIs with site key).
  6. Server path: store server credentials only on the backend; call POST /api/v1/server/notifications/ or MKPClient.notifications.send.
  7. Send a test (admin test-send and/or real submit/API call); confirm EmailDelivery rows in admin.
  8. In production, the platform must have a working Django EMAIL_BACKEND / SMTP (or equivalent). Client sites do not configure SMTP for this feature. Use Allowed Domains (not Repl IDs) for real product hosts.


LLM implementation brief

Copy everything in this section into another chat when you want an LLM to implement MKP email on a different client codebase. Fill in the bracketed values first.

# Task: Implement MKP Works Platform transactional email on this client site

## Product facts (do not invent alternatives)
- MKP Works Platform owns outbound template-based transactional email.
- Client sites do NOT install SMTP/SendGrid/Resend/Nodemailer/Mailgun/SES SDKs for this feature.
- There is NO inbound mailbox API.
- Browser code MUST NOT call the server notifications API and MUST NEVER contain server secrets.
- Platform base URL: {{PLATFORM_BASE_URL}}  (example: https://mkpworks.com)
- Client site slug: {{SITE_SLUG}}
- Public site key (browser only): {{SITE_KEY}}  # pk_...
- Server client id: {{CLIENT_ID}}              # skpub_...
- Server client secret: {{CLIENT_SECRET}}      # sk_...  (backend env only)
- Allowed browser origin / domain: {{CLIENT_ORIGIN}}  # AllowedDomain in admin, OR (Replit dev) saved Repl ID
- Optional client Replit REPL_ID for platform ClientSite.replit_ids: {{CLIENT_REPL_ID}}
- Site support email (platform admin field): {{SUPPORT_EMAIL}}
- If testing from Replit: platform must use development settings; admin ClientSite.replit_ids must include {{CLIENT_REPL_ID}} from `echo $REPL_ID` on the client Repl; mkp.js baseUrl must be the platform Repl HTTPS origin.

## Choose integration mode for this site
Mode: {{FORMS | SERVER_NOTIFICATIONS | BOTH}}

### Mode FORMS — browser form submit triggers email
Platform admin must already have:
- Active FormDefinition slug: {{FORM_SLUG}}  (example: contact)
- Optional confirmation EmailTemplate code linked on the form
- Optional notification EmailTemplate code linked on the form
- ClientSite.support_email set if staff notification is required

Client install:
1. Load hosted script (no npm package, no JS dependencies):
   <script src="{{PLATFORM_BASE_URL}}/sdk/v1/mkp.js"></script>
2. Init and mount:
   MKP.init({ baseUrl: "{{PLATFORM_BASE_URL}}", siteKey: "{{SITE_KEY}}" });
   <div data-mkp="form" data-form="{{FORM_SLUG}}"></div>
3. Alternatively, without mkp.js, call the REST APIs below with fetch/XHR from the browser using only X-MKP-Site-Key.

APIs:
- GET  {{PLATFORM_BASE_URL}}/api/v1/forms/{{FORM_SLUG}}/
  Headers: X-MKP-Site-Key: {{SITE_KEY}}
- POST {{PLATFORM_BASE_URL}}/api/v1/forms/{{FORM_SLUG}}/submissions/
  Headers:
    X-MKP-Site-Key: {{SITE_KEY}}
    Origin: {{CLIENT_ORIGIN}}
    Content-Type: application/json
  Body (JSON):
  {
    "data": {
      "name": "<string>",
      "email": "<string>",
      "message": "<string>",
      "_hp": "",
      "_t": "<unix_timestamp_float_as_string>"
    },
    "source_url": "{{CLIENT_ORIGIN}}/..."
  }
  Expected: HTTP 201 JSON with id, status, email, created_at, spam_score.
  Side effect: if status is received and email present, platform sends linked confirmation/notification templates.
  Do not implement a separate "send email" call in the browser.

### Mode SERVER_NOTIFICATIONS — trusted backend sends named templates
Platform admin must already have EmailTemplate rows with codes you will call, e.g.:
- {{TEMPLATE_CODE}}  (example: forms.submission_received)
Recipient mode on each template matters:
- submitted_email → uses request recipient
- site_support_address → forced to ClientSite.support_email
- configured_address → forced to template.configured_recipient
- authenticated_user / staff_only → special cases; prefer submitted_email or configured_address for product mail

Client install (Python preferred):
- Copy/install platform package from MKP repo path sdk/python/mkp_client
- Dependency: httpx
- Env vars on the CLIENT BACKEND only:
  MKP_BASE_URL={{PLATFORM_BASE_URL}}
  MKP_CLIENT_ID={{CLIENT_ID}}
  MKP_CLIENT_SECRET={{CLIENT_SECRET}}

Python usage:
  from mkp_client import MKPClient
  client = MKPClient(base_url=MKP_BASE_URL, client_id=MKP_CLIENT_ID, client_secret=MKP_CLIENT_SECRET)
  client.notifications.send("{{TEMPLATE_CODE}}", "user@example.com", context={...})

REST (any language):
  POST {{PLATFORM_BASE_URL}}/api/v1/server/notifications/
  Headers:
    X-MKP-Client-Id: {{CLIENT_ID}}
    X-MKP-Client-Secret: {{CLIENT_SECRET}}
    Content-Type: application/json
    Accept: application/json
  Body:
  {
    "template": "{{TEMPLATE_CODE}}",
    "recipient": "user@example.com",
    "context": { "any": "keys referenced by the Django email templates" }
  }
  Response 200:
  { "status": "sent" | "failed", "recipient": "u***@example.com" }
  Treat status != "sent" as delivery failure in app logic. Recipient is masked.

## Hard constraints for the implementing agent
1. Do not add email provider packages to the client site for MKP mail.
2. Do not expose sk_ / Client-Secret to frontend, mobile apps, or public repos.
3. Do not create client-side endpoints that proxy arbitrary free-form email; only call MKP with template codes.
4. Match Origin / domain allowlist for form submits.
5. Template HTML/subject/body are authored in MKP Django Admin, not in the client repo (unless this task explicitly includes asking an operator to create them).
6. Platform login has no email verification step — do not confuse transactional mail with people auth.

## Acceptance criteria
- [ ] Chosen mode(s) work against {{PLATFORM_BASE_URL}}
- [ ] Secrets only in backend env for SERVER_NOTIFICATIONS
- [ ] Forms path uses site key + mkp.js or documented form APIs only
- [ ] Successful send visible as EmailDelivery in platform admin (masked recipient)
- [ ] No client SMTP configuration required