Skip to content
TheShopMarketing Guys

Email and text campaigns

Email or text customers who've said yes to hearing from you. Nothing sends without your OK.

Overview

Your past customers already know you. A well-timed email or text, like a service reminder or open slots this week, brings them back without paying for a new click. Your assistant can send those for you, and it checks with you before anything goes out.

First it looks at what's worked before and who's agreed to hear from you. It also pulls your email style: your logo, your colors, and the address your emails come from. Then it writes the email or text and shows you how it'll look. It can send a test to your own email and phone.

Before anything reaches customers, it tells you exactly how many people will get it. You confirm that number to send or schedule. If the list changes after you look, it stops and asks again. Texts only go out during local sending hours. There's also a limit on how often one customer gets a promo text.

Sending to customers turns on once your plan's billing has started. Drafts, previews, and tests work before that.

The tools on this page

Email and text campaigns: what each tool reads, writes, and asks
ToolReadsWritesAsks you first
See your campaignsYesNoNo
Your email styleYesNoNo
Read a past emailYesNoNo
Draft a campaignNoYesYes
Preview an emailYesNoNo
Send a testNoYesYes
See who gets itYesNoNo
Send a campaignNoYesAlways
Schedule or cancelNoYesYes

See your campaigns

get_campaign_contextReads only

What you can ask

  • How did last month's email do?
  • Who can I text right now?
  • What address do my emails come from?

What it does

Your recent campaigns and how they did, who you can email or text, and the address your emails come from.

What your assistant sends

FieldRequiredDescription
limitintegerNoRecent campaigns to include, default 10.
Example: what your assistant sends
{
  "limit": 5
}
Example: what comes back
{
  "business": "Example Service Group",
  "timezone": "America/New_York",
  "channels": {
    "email": {
      "available": true
    },
    "sms": {
      "available": true,
      "sending_numbers": [
        {
          "id": "77777777-7777-4777-8777-777777777777",
          "number": "+19195550142",
          "default": true,
          "status": "active",
          "campaign": "Summer tune-ups"
        }
      ],
      "send_window": "Weekdays 9:00 to 17:00, local time",
      "promotional_cap": {
        "used": 1,
        "cap": 4,
        "window_days": 30
      }
    }
  },
  "from": {
    "name": "Example Service Group",
    "email": "info@mail.esg.example",
    "reply_to": "hello@esg.example"
  },
  "consent": {
    "contacts": 1280,
    "email_consented": 1034,
    "sms_promotional_consented": 412,
    "opted_out": {
      "sms": 9,
      "email": 21
    }
  },
  "segments": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "name": "Maintenance members",
      "type": "static",
      "contacts": 318
    }
  ],
  "data": {
    "recent_campaigns": [
      {
        "id": "44444444-4444-4444-8444-444444444444",
        "name": "Summer tune-ups",
        "status": "sent",
        "channels": [
          "email"
        ],
        "scheduled_for": null,
        "sent_at": "2026-09-01T14:00:00.000Z",
        "stats": {
          "total_sent": 409,
          "total_failed": 3,
          "email_sent": 409,
          "email_delivered": 401
        },
        "results": {
          "email": {
            "sent": "409",
            "delivered": "401",
            "opened": "n/a",
            "clicked": "n/a",
            "replied": "n/a",
            "opt_outs": "n/a"
          },
          "caveats": []
        }
      }
    ],
    "results_caveats": [
      "Open and click tracking is off to protect deliverability, so opened and clicked show n/a."
    ]
  }
}

Your email style

get_email_brand_kitReads only

What you can ask

  • What should my emails look like?
  • Use my logo and colors in this email.

What it does

How your emails should look: your colors, your logo, the address they come from, and the layout to start from.

What your assistant sends

Nothing. Your assistant just runs it.

Example: what your assistant sends
{}
Example: what comes back
{
  "business": {
    "name": "Example Service Group",
    "address": "12 Main St, Raleigh, NC 27601",
    "website": "https://esg.example"
  },
  "from": {
    "name": "Example Service Group",
    "email": "info@mail.esg.example",
    "reply_to": "hello@esg.example"
  },
  "phone": {
    "display": {
      "variable": "{{company_phone}}",
      "resolves_to": "(919) 555-0142"
    },
    "tel_links": {
      "variable": "{{company_phone_tracking}}",
      "resolves_to": "(919) 555-9999"
    },
    "rule": "Show the number with the display variable. Every tel: href must use the tracking variable, or the call is not credited to this email. A number typed into the HTML is refused."
  },
  "greeting": {
    "variable": "{{first_name}}",
    "rule": "Put the greeting in its own paragraph, for example <p>Hi {{first_name}},</p>."
  },
  "logo": {
    "light_background": {
      "variable": "{{company_logo_light}}",
      "resolves_to": "https://cdn.esg.example/logo.png"
    },
    "dark_background": {
      "variable": "{{company_logo_dark}}",
      "resolves_to": null
    }
  },
  "theme": {
    "brand": "#0b5d3b",
    "brandInk": "#e8c547",
    "accent": "#f59e0b",
    "accentInk": "#111827",
    "ink": "#111827",
    "ink70": "#585d68",
    "inkFaint": "#94979e",
    "surface": "#ffffff",
    "surfaceAlt": "#f5f6f6",
    "hairline": "#e2e3e5",
    "cta": "#f59e0b",
    "ctaInk": "#111827",
    "kickerColor": "#f59e0b",
    "danger": "#ef4444",
    "warning": "#f59e0b",
    "radius": 8,
    "radiusControl": 8,
    "radiusCard": 8,
    "elevation": "hairline",
    "headingWeight": 700,
    "headingTracking": "normal",
    "radiusCss": "8px",
    "radiusControlCss": "8px",
    "radiusCardCss": "8px",
    "arcsize": "32%",
    "headingFontStack": "'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif",
    "bodyFontStack": "'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif",
    "webFontImport": "<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">",
    "rules": "Brand, accent, and kicker colors are TEXT colors, never backgrounds."
  },
  "design": {
    "language": "ledger",
    "rules": [
      "One continuous bordered card; every section is a row inside it.",
      "Keep the version marker <!-- tmg-email:ledger-v1 --> exactly once, right after <body>."
    ],
    "scaffold": "<!DOCTYPE html><html><body><!-- tmg-email:ledger-v1 --><table role=\"presentation\">...</table></body></html>"
  },
  "call_to_action": {
    "url": "https://esg.example/contact",
    "alternatives": {
      "website": "https://esg.example",
      "call": "tel:{{company_phone_tracking}}"
    }
  },
  "images": {
    "rule": "Only your own library. Call list_media, then use media://<media_id> as the img src."
  },
  "unsubscribe": {
    "rule": "Do not write one. The required footer is appended to every marketing email at send time."
  },
  "next_step": "Draft from the scaffold, then preview_email."
}

Read a past email

get_campaign_emailReads only

What you can ask

  • Show me the email we sent in July.
  • Write this one in the same tone as our last newsletter.

What it does

One past email, word for word, so a new one can match it.

What it needs

  • The past campaign you want to see.

What your assistant sends

FieldRequiredDescription
campaign_idstringYesA campaign from get_campaign_context. Prefer status sent. Tone and content reference only; layout comes from the get_email_brand_kit scaffold.
Example: what your assistant sends
{
  "campaign_id": "44444444-4444-4444-8444-444444444444"
}
Example: what comes back
{
  "campaign_id": "44444444-4444-4444-8444-444444444444",
  "status": "sent",
  "sent_at": "2026-09-01T14:00:00.000Z",
  "data": {
    "name": "Summer tune-ups",
    "email_subject": "{{first_name}}, your summer tune-up is due",
    "email_html": "<html><body><p>Hi {{first_name}},</p><p>Call {{company_phone}}.</p></body></html>"
  },
  "note_on_style": "This campaign predates the current design language. Use it for tone and content only; the layout to follow is the scaffold in get_email_brand_kit."
}

Draft a campaign

draft_campaignChanges your site or sends something

What you can ask

  • Draft an email about our fall tune-up special.
  • Write a text to our maintenance members about open slots this week.

What it does

Draft an email, a text, or both. Never sends or schedules.

Your assistant asks you to confirm this first.

What it needs

  • A name for the campaign, just for you.
  • Email, text, or both.

What it won't do

It only drafts. Nothing goes out until you send it or schedule it. A text needs one of your sending numbers, and your assistant can look those up.

What your assistant sends

FieldRequiredDescription
namestringYesInternal campaign name.
channelsarray of one of: email, smsYesChannels for this campaign.
email_subjectstringNoRequired when channels include email.
email_htmlstringNoRequired when channels include email.
sms_messagestringNoRequired when channels include sms. A STOP line is appended for you.
segment_idstringNoA segment from get_campaign_context. Omitted means every contact.
tracking_phone_number_idstringNoThe sending number for SMS. Required before an SMS campaign can send.
scheduled_forstringNoISO timestamp. SMS sends only inside the local weekday 9:00-17:00 window.
owner_requeststringNoWhat the owner asked for, in their words, one sentence. Optional. Stored with the audit record so their team can see what owners ask for.
Example: what your assistant sends
{
  "name": "Fall tune-up",
  "channels": [
    "email"
  ],
  "email_subject": "Fall tune-up time",
  "email_html": "<p>Book your tune-up.</p>"
}
Example: what comes back
{
  "campaign": {
    "id": "44444444-4444-4444-8444-444444444444",
    "name": "Fall tune-up",
    "status": "draft",
    "channels": [
      "email"
    ],
    "scheduled_for": null,
    "next_step": "preview_email, then preview_recipients"
  }
}

Preview an email

preview_emailReads only

What you can ask

  • Show me how this email looks before it goes out.
  • Send me a link so I can check the draft on my phone.

What it does

Show an email the way it will arrive. Saves nothing.

What your assistant sends

FieldRequiredDescription
campaign_idstringNoPreview a saved draft. The rendered result is what send_campaign will dispatch.
email_subjectstringNoWith email_html, previews unsaved copy. Saves nothing.
email_htmlstringNoWith email_subject, previews unsaved copy. Saves nothing.
Example: what your assistant sends
{
  "campaign_id": "44444444-4444-4444-8444-444444444444"
}
Example: what comes back
{
  "mode": "draft",
  "campaign_id": "44444444-4444-4444-8444-444444444444",
  "data": {
    "subject": "Sarah, fall tune-up time",
    "html": "<!DOCTYPE html><html><body>...</body></html>"
  },
  "warnings": [
    {
      "code": "no_logo_variable",
      "message": "The email has no logo. Use {{company_logo_light}} in the header."
    }
  ],
  "preview_url": "https://app.example.com/preview/email/<token>",
  "expires_in_minutes": 30,
  "note": "This is the stored draft rendered by the send path, so it is what your contacts will receive. Names and the unsubscribe link are samples."
}

Send a test

send_campaign_testChanges your site or sends something

What you can ask

  • Send me a test of that email.
  • Text the draft to my phone first.

What it does

Send a test of a draft to your own email and phone only.

Your assistant asks you to confirm this first.

What it needs

  • The draft you want to test.

What it won't do

Tests only go to your own email and phone. It can't send a test to anyone else, so a test can't turn into a real send.

What your assistant sends

FieldRequiredDescription
campaign_idstringYesThe draft to test.
channelsarray of one of: email, smsNoDefaults to every channel on the campaign.
owner_requeststringNoWhat the owner asked for, in their words, one sentence. Optional. Stored with the audit record so their team can see what owners ask for.
Example: what your assistant sends
{
  "campaign_id": "44444444-4444-4444-8444-444444444444"
}
Example: what comes back
{
  "sent": [
    "email"
  ],
  "skipped": [
    {
      "channel": "sms",
      "reason": "The campaign has no SMS text yet."
    }
  ],
  "sent_to": "your own address and number only"
}

See who gets it

preview_recipientsReads only

What you can ask

  • How many people will this email reach?
  • Show me exactly what the text will say before it goes out.

What it does

How many people a draft will reach, by email and by text, and exactly what the text says.

What it needs

  • The draft you want to check.

What your assistant sends

FieldRequiredDescription
campaign_idstringYesThe draft to preview.
Example: what your assistant sends
{
  "campaign_id": "44444444-4444-4444-8444-444444444444"
}
Example: what comes back
{
  "campaign_id": "44444444-4444-4444-8444-444444444444",
  "channels": [
    "email"
  ],
  "counts": {
    "reachable": 412,
    "email": 412,
    "sms": 0,
    "both": 0,
    "unreachable": 37
  },
  "opted_out": {
    "email": 21,
    "sms": 0
  },
  "from": {
    "name": "Example Service Group",
    "email": "info@mail.esg.example"
  },
  "sms_from": null,
  "data": {
    "sample": [
      {
        "first_name": "Dana",
        "channel": "email"
      },
      {
        "first_name": null,
        "channel": "email"
      }
    ],
    "sms_preview": null
  },
  "confirm_with_recipient_count": 412
}

Send a campaign

send_campaignChanges your site or sends something

What you can ask

  • Send the tune-up email now.
  • Go ahead and send it to everyone on the list.

What it does

Send a draft to everyone it reaches, now.

This asks you first, every time.

What it needs

  • The draft you want to send.
  • The number of people you saw in the preview.

What it won't do

It won't send if the number of people changed since you looked. Texts only go out during local sending hours. There's a limit on how often one customer gets a promo text.

What your assistant sends

FieldRequiredDescription
campaign_idstringYesThe draft to send.
confirmobjectYesRequired. Call preview_recipients first and pass the count you saw.
idempotency_keystringNoOptional. Reusing a key within 24 hours returns the first send's result instead of sending again.
owner_requeststringNoWhat the owner asked for, in their words, one sentence. Optional. Stored with the audit record so their team can see what owners ask for.
Example: what your assistant sends
{
  "campaign_id": "44444444-4444-4444-8444-444444444444",
  "confirm": {
    "recipient_count": 412,
    "intent": "Send the fall tune-up offer to everyone"
  },
  "idempotency_key": "fall-tuneup-0930"
}
Example: what comes back
{
  "campaign_id": "44444444-4444-4444-8444-444444444444",
  "sent": 409,
  "failed": 3,
  "email": {
    "sent": 409,
    "failed": 3
  },
  "sms": {
    "sent": 0,
    "failed": 0
  }
}

Schedule or cancel

manage_campaignChanges your site or sends something

What you can ask

  • Schedule the tune-up email for Tuesday at 9am.
  • Cancel the campaign that's going out Friday.
  • Archive the old spring promo.

What it does

Schedule, reschedule, cancel, or archive a campaign.

Your assistant asks you to confirm this first.

What it needs

  • The campaign.
  • What to do with it: schedule, reschedule, cancel, or archive.

What it won't do

Scheduling asks you to confirm how many people get it, same as sending now. A campaign that's in the middle of sending can't be changed. Cancelling a scheduled one puts it back in your drafts.

What your assistant sends

FieldRequiredDescription
campaign_idstringYes
actionone of: schedule, reschedule, cancel, archiveYescancel returns a scheduled campaign to draft. archive retires a draft.
scheduled_forstringNoRequired for schedule and reschedule.
confirmobjectNoRequired for schedule and reschedule: pass the count from preview_recipients. The send is held if the list grows past it.
owner_requeststringNoWhat the owner asked for, in their words, one sentence. Optional. Stored with the audit record so their team can see what owners ask for.
Example: what your assistant sends
{
  "campaign_id": "44444444-4444-4444-8444-444444444444",
  "action": "schedule",
  "scheduled_for": "2026-10-06T14:00:00.000Z",
  "confirm": {
    "recipient_count": 412,
    "intent": "Send Monday morning"
  }
}
Example: what comes back
{
  "campaign_id": "44444444-4444-4444-8444-444444444444",
  "status": "scheduled",
  "scheduled_for": "2026-10-06T14:00:00.000Z",
  "recipient_count": 412
}
Send email and text campaigns through your assistant | The Shop Marketing Guys