# Email - Sending & Formatting

## Two distinct email channels

Gipity has two separate email paths; use the one matching who the recipient should see as the sender.

| Dimension | Platform email | User's Gmail |
|---|---|---|
| CLI | `gipity email send` | `gipity gmail send|reply|search|read` |
| Sender address | `gipity@gipity.ai` | the user's own Gmail address |
| Quota / deliverability | Gipity platform quota | User's Gmail quota |
| Requires | Nothing extra | A connected Google account |
| Use when | The platform owns the message (reminders, heartbeat alerts, platform notifications) | The message should come from the user personally (replies to Gmail threads, outreach from their own address) |

**Default is `gipity email send`.** Only use `gipity gmail` when the user explicitly asks to send from their own Gmail, or when you're replying to an existing Gmail thread.

**These are CLI commands, for you while you build.** A *deployed app* sends email from a function with the injected **`email()`** service: declare `services: ['email']` and call `await email({ to, subject, text })`. The platform brokers delivery - owner-billed, one credit per recipient actually sent. The From **address** is platform-controlled (`gipity@gipity.ai`) for deliverability and anti-spoofing; the app may set only the display **name** (`fromName`) and `replyTo` (so replies land in the owner's inbox). Unsubscribed/blocked recipients are skipped and not charged. Full reference: the [app-development](app-development.md) skill.

```js
// functions/send-welcome.js   (gipity.yaml → services: ['email'])
export default async function (ctx, { email }) {
  const r = await email({
    to: 'lead@example.com',      // string or array (max 50)
    subject: 'Thanks for signing up',
    text: 'We\'ll be in touch shortly.',   // and/or html
    replyTo: 'you@yourco.com',   // optional - replies thread to you
    fromName: 'Acme Sales',      // optional display name
  });
  return r; // { sent: 1, skipped: 0, results: [{ to, status: 'sent' }] }
}
```

Under `gipity test`, `email()` returns `{ sent: 0, skipped: 0, results: [], test: true }` without sending. Verify the real send path after deploy with `gipity email test <to>` (skipped recipients say why: unsubscribed, blocked test/internal address, or server not configured); `gipity email log` lists recent sends AND skipped attempts.

**Prefer a workflow `notify` step when the email is event- or schedule-driven** - "email me when someone submits the form" (a *record insert*) or a *scheduled digest* - so no function has to run. The complete record-triggered recipe:

```yaml
# workflows/contact-notify.yaml - email the owner on each new contact-form row
name: contact-notify
trigger: record.after_insert
table: contact_messages
steps:
  - name: email_owner
    step_type: notify
    config:
      channel: email
      to: owner@example.com
      subject: "New contact message from {{trigger.record.name}}"
      body: "{{trigger.record.email}} wrote:\n\n{{trigger.record.message}}"
```

plus a `workflows` phase in `gipity.yaml` (`type: workflows`, `source: workflows`) so `gipity deploy` reconciles it. Three requirements make the record trigger fire: the workflow needs `table:`, the row must be written through the Records API (not raw SQL in a function), and an anonymous-visitor form needs the table set to `auth_level: public`. For a scheduled email instead of a record-triggered one, swap the trigger for `trigger: scheduled` + `cron:`. Details (step types, template variables, limits): [workflow](workflow.md).

- Omit `--to` on `gipity email send` to send to the user's own email.

## Plain characters only
Use plain ASCII characters only. No em dashes, en dashes, smart/curly quotes, ellipsis, non-breaking spaces, or other fancy Unicode punctuation. Use a hyphen (-), straight double quote ("), straight apostrophe ('), and three periods (...). Applies to everything you write for the user: chat responses, email drafts and bodies, file content, memory, commit messages, code comments. Exception: if the user explicitly asks for specific characters or a particular style (for example "use em dashes in this poem", "make it fancy"), follow their lead - they are in charge of their own content.

## Replying to a Gmail thread
- Always `gipity gmail read <message-id>` the message you're replying to first - you need its sender, date, and full body to build the quote.
- The body you pass to `gipity gmail reply` MUST be: the new reply text, a blank line, then a standard quote header `On <date>, <sender> wrote:`, then the prior message body line-prefixed with `> `. Example:

  ```
  Sounds good - Tuesday at 2pm works for me.

  On Wed, Apr 17, 2026 at 9:14 AM, Alice <alice@example.com> wrote:
  > Can we move the sync to Tuesday?
  > Let me know what time works.
  ```

- Quote the message you're directly replying to (the most recent one), and leave any quoted chain it already contains intact - don't strip history.
- Default to including the quote. Skip it only if the user explicitly says so (e.g. "just send 'yes', don't quote").
- For `subject`, prefix the original subject with `Re: ` if it isn't already.

## How gipity email send works
- It sends as soon as you run it. Only send to someone other than the user when the user asked for that email.
- Every external email includes an unsubscribe link; recipients can block future emails from the sender.
- Self-sends (omit `--to`) go to the user's own address.
- A platform footer is appended to every email.

### Rate limits
- **Max 10 emails per 1 hour** per account
- Plan batch sends accordingly - space them out if sending to multiple recipients

### Options
- `--to` - recipient (repeatable; omit for self-send), `--cc`, `--bcc`, `--reply-to`
- `--subject` - subject line (required)
- `--body` - plain text body (required)
- `--html` - optional HTML body, used as the rich version alongside plain text

## HTML Style Rules
- **Inline styles only** - email clients strip `<style>` tags. Every element needs `style="..."`.
- **System fonts**: `font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`
- **Single-column layout** - centered `<div>` or `<table>` with `max-width: 600px`. No flexbox/grid.
- **Minimal colors** - dark text (#333) on white (#fff). One accent color max.
- **Font sizes**: 14-16px body, 20-24px headings, never below 12px.

## Images
- **Default: transcode before emailing.** For a user-supplied image (photo, screenshot, render), in the sandbox resize to 800px max on any side and convert to JPG at quality 85. `convert input.png -resize '800x800>' -quality 85 output.jpg` (the `>` only shrinks, never enlarges). A 1.6MB phone photo typically drops to ~100–200KB. Then get a public URL for the transcoded version with `gipity file url <path>` and link/embed that.
- **Send the original only if the user asks** - "send the original", "don't compress", "full resolution", etc. If the input is already small and reasonable (under ~500KB and ~1200px), you can skip the transcode.
- **Never SVG** - email clients strip it. Convert to PNG/JPG first: `cairosvg` (Python) or ImageMagick.
- **Never base64** - email clients strip `data:` URIs. Always host it (`gipity file url <path>`) and link: `<img src="https://..." width="..." height="...">`.
- **HTML display width**: use `width="600"` (or less) on the `<img>` - 600px is the standard single-column email layout width.

## Preview
- Preview rich/styled HTML emails before sending: save the HTML into the project, get its URL with `gipity file url <path>`, and check it with `gipity page screenshot <url>`.
- Simple text-only emails don't need a preview step.
- Same hosted URLs work in both preview and email.
- 3MB preview limit - compress images in sandbox if needed.
