All playbooks
Playbook 1 of 6

Generate your Messy client

Every integration starts with a client. Paste one prompt into your coding agent and it builds a Messy client in your app's language, matching your conventions, then wires it into your queue so sends never block a request.

10 min You ship: A MessyClient in your codebase plus a background job wrapper, used by every playbook after this one.
1

Generate the client

You need a Messy account and an environment API key (Channels page in the dashboard). Set MESSY_API_URL and MESSY_API_KEYin your app's environment before you start.

This prompt carries the complete API contract, so your agent needs no external docs. It reads your codebase first and writes the client the way the rest of your code is written.

01 · generate the client
You are working in my application codebase. Look at the project structure first and follow its existing conventions for configuration, HTTP calls and naming.

Build a small client for Messy, the messaging platform we use for email, SMS, WhatsApp and push. Requirements:

Configuration
- Read two required environment variables at construction time: MESSY_API_URL (for example https://api.messy.sh) and MESSY_API_KEY. Fail fast with a clear error if either is missing.
- Every request sends the headers "Authorization: Bearer <MESSY_API_KEY>" and "Content-Type: application/json". Bodies are JSON.

Methods
1. trigger(trigger, to, data = {}, channel = "email", cc = null, bcc = null)
   POST <MESSY_API_URL>/messages/trigger
   Body: {"trigger": "login.otp", "channel": "email", "message": {"to": "user@example.com"}, "data": {"name": "Sam", "otp_code": "123456"}}
   Include cc/bcc inside "message" only when set. This is the main transactional call: Messy renders the template registered under the trigger key with the "data" variables and delivers it.

2. send_message(type, to, body, subject = null, cc = null, bcc = null)
   POST <MESSY_API_URL>/messages
   Body: {"type": "email", "message": {"to": "...", "subject": "...", "body": "..."}}
   "type" is one of email, sms, whatsapp, mobile_push, web_push. A raw send with no template.

3. identify(email, first_name = null, last_name = null, custom_attributes = {})
   POST <MESSY_API_URL>/customers/identify
   Body: {"email": "...", "first_name": "...", "custom_attributes": {"plan": "pro", "country": "NL"}}
   Upserts the customer profile keyed on email. custom_attributes is a flat JSON object that Messy shallow-merges into the profile; these keys power audience segments later.

4. register_device(email, token, platform, device_id = null, app_id = null, device_name = null) and unregister_device(token)
   POST <MESSY_API_URL>/device_tokens and POST <MESSY_API_URL>/device_tokens/unregister. Only needed if we send mobile or web push.

Error handling
- 2xx: parse the JSON body, treat an empty body as an empty object.
- 401: raise a single custom error type (MessyError) with message "Unauthorized: invalid Messy API key".
- 422: raise MessyError including the "error" or "errors" value from the response body.
- Anything else: raise MessyError with the status code and raw body.

Keep it dependency-free: use the standard library HTTP client, no SDK or wrapper package. When you are done, list the files you created and show one usage example per method.
2

Wrap it in your queue

Transactional sends should never block a web request. One thin job routes every client call through your existing queue with retries.

02 · background wrapper
Add a background wrapper for the MessyClient you just built, using this project's existing job or queue system (look for one before adding anything new).

- One generic job, for example MessyTriggerJob, that takes a client method name plus its arguments and calls that method on a fresh client.
- Retry on any error with exponential backoff, up to 5 attempts, using the queue's native retry mechanism.
- Add a small convenience so call sites read cleanly, for example MessyTriggerJob.enqueue("trigger", ...args).
- Rule of thumb for later call sites: everything goes through the job, except time-critical sends like OTP codes, which call the client synchronously in the request path so a failure surfaces to the user immediately.

Show me the diff and one example call site.
3

Verify with one real send

Before building on top, prove the pipe works. Send yourself an email; the response status will be pending (queued), then sent once delivered. You can watch it land in the Messy dashboard under Transactional.

smoke-test.sh
curl -s -X POST https://api.messy.sh/messages \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $MESSY_API_KEY" \
  -d '{
    "type": "email",
    "message": {
      "to": "you@yourdomain.com",
      "subject": "Messy client check",
      "body": "<p>It works.</p>"
    }
  }'