← back to Dw Vendor Mailer

build.py

226 lines

#!/usr/bin/env python3
"""
Build the DW "Houses We Represent" Constant Contact mailer.
Clones the Sparkle & Shine layout: 6 vendor rows, each headed by the vendor's
LOGO (image for the 5 rep houses, live text wordmark for Phillipe Romano),
with 4 products per row, each linking to its Shopify page for a $4.25 sample.

All product + collection URLs verified 200 on 2026-09-09.
Regenerate:  python3 build.py   ->  mailer.html
"""
import html, pathlib

STORE = "https://designerwallcoverings.com"
NEW_ARRIVALS = STORE + "/collections/new-arrivals"
DW_LOGO = "https://www.designerwallcoverings.com/cdn/shop/files/Screen_Shot_2019-02-06_at_12.08.24_PM_525x300.png?v=1613539653"

# Each vendor: display name, logo (image url OR {"wordmark": text}), collection handle, 4 products.
VENDORS = [
    {
        "name": "Phillipe Romano",
        # PR's brand logo IS a text wordmark (Playfair Display serif, #221f1c) -> render as live text.
        "wordmark": "Phillipe Romano",
        "tagline": "From our own atelier — Milano metallics & grasscloth.",
        "collection": "phillipe-romano",
        # distinct patterns (newest), not colorways of one
        "products": [
            ("La Mayorca Flock", "la-mayorca-flock-wallcovering-69430",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/f1a10209afac0ebb07e92b17b292341f.jpg?v=1781658964"),
            ("Regalita Nera — Black", "regalita-nera-black-wallcovering-phillipe-romano",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/MI739_MI739_MI739.jpg?v=1775512460"),
            ("Filigrana Dorata — Gold", "filigrana-dorata-gold-wallcovering-phillipe-romano",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/MI734_MI734_MI734.jpg?v=1775512459"),
            ("Trama Rossa — Red", "trama-rossa-red-wallcovering-phillipe-romano",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/MI730_MI730_MI730.jpg?v=1775512458"),
        ],
    },
    {
        "name": "Thibaut",
        "logo": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/thibaut.png?v=1788980975",
        "logo_w": 250,
        "tagline": "American heritage prints & grounds.",
        "collection": "thibaut-wallcoverings",
        # distinct patterns (newest), not colorways of one
        "products": [
            ("Onda — Pearl & Silver", "onda-pearl-and-silver-dwtt-73419",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/AT7900_ee1a8674-8c14-4d27-85ee-af82dcbed06c.jpg?v=1782268872"),
            ("Gibson — Navy", "gibson-navy-dwtt-73418",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/AT78793_995d5590-271c-4116-8877-814c9e53bb9a.jpg?v=1782268867"),
            ("Gada Paisley — Green", "gada-paisley-green-dwtt-73415",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/AT78784.jpg?v=1782261621"),
            ("Kahna — Cream/Robin's Egg", "kahna-cream-and-robin-s-egg-dwtt-73413",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/AT78781_b26586b0-2c6c-459d-b67e-1574514693d3.jpg?v=1782258045"),
        ],
    },
    {
        "name": "Cole & Son",
        "logo": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/cole-son.png?v=1788980977",
        "logo_w": 190,
        "tagline": "Iconic British archive & botanicals.",
        "collection": "cole-and-son",
        "products": [
            ("Palm Jungle — Olive/White", "palm-jungle-olive-green-on-white-cole-son-contemporary-restyled-botanical-floral-wallcovering",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/PalmJungle_Image_Flatshot_Item_95-1001.jpg?v=1779809928"),
            ("Seasonal Woods — Platinum", "seasonal-woods-silk-platinum-cole-son",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/seasonal-woods-silk-platinum-cole-son-37235620446259.jpg?v=1785462973"),
            ("Verdure Tapestry — Silk", "verdure-tapestry-silk-pts-l-cole-son",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/verdure-tapestry-silk-pts-l-cole-son-37223445364787.jpg?v=1785464400"),
            ("Protea Garden — Silk", "protea-garden-silk-olvgrn-tngrn-wht-cole-son",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/119_10045_CS_dab7e6e6-dd85-439c-8a7e-52c91452a607.jpg?v=1776794876"),
        ],
    },
    {
        "name": "Brunschwig & Fils",
        "logo": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/brunschwig-fils.png?v=1788980979",
        "logo_w": 150,
        "tagline": "Timeless texture & French elegance.",
        "collection": "brunschwig-fils",
        "products": [
            ("Yasu — Silver", "yasu-silver-brunschwig-fils",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8015111_11_926bf78a-0113-4b3d-a601-d75cad06b09c.jpg?v=1777019993"),
            ("Aya — Silver", "aya-silver-brunschwig-fils",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8015110_11_d7f516c7-b57d-46e0-af8a-a2b65ba5848c.jpg?v=1776818900"),
            ("Haizu — Silver", "haizu-silver-brunschwig-fils",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8015109_11_107b9977-9cb8-45c1-89c2-d3c128ab661a.jpg?v=1776795793"),
            ("Ryo — Pewter Grey", "dwkk-102144",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/P8015138_11_c794772e-4391-4d08-87a6-cc60eea03de7.jpg?v=1753290530"),
        ],
    },
    {
        "name": "Designers Guild",
        "logo": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/designers-guild.png?v=1788980984",
        "logo_w": 300,
        "tagline": "Colour-led damasks & florals.",
        "collection": "designers-guild-wallpaper",
        # Outlook-safe: DG's catalog is ~99% WebP; these 3 are the JPG-imaged patterns.
        "products": [
            ("Surimono Watercolour — Rose", "eur-90365-designer-wallcoverings-los-angeles",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/96434.jpg?v=1782461438"),
            ("Emilie Botanical — Platinum", "eur-80735-pdg1050-designer-wallcoverings-los-angeles",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/91544.jpg?v=1782461419"),
            ("Angelique Damask — Blue", "eur-80706-pdg1036-designer-wallcoverings-los-angeles",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/82097.jpg?v=1782461625"),
        ],
    },
    {
        "name": "Osborne & Little",
        "logo": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/osborne-little-shopify.png?v=1788980982",
        "logo_w": 100,
        "tagline": "London design house — pattern & stripe.",
        "collection": "osborne-little",
        "products": [
            ("Fitzroy — White/Stone", "fitzroy-white-stone-osborne-little",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/W6295-06.jpg?v=1781902811"),
            ("Karabali — Teal/Russet", "karabali-teal-russet-osborne-little",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/NCW4597-04.jpg?v=1781902807"),
            ("Bramante Stripe", "bramante-stripe-wallcovering-osborne-little",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bramante-stripe.jpg?v=1760735635"),
            ("Quartz", "eur-71338-designer-wallcoverings-los-angeles",
             "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/cw5410-09.jpg?v=1782461570"),
        ],
    },
]

INK = "#221f1c"
MUTED = "#8a8178"
LINE = "#e7e2db"
CREAM = "#f7f5f2"
SERIF = "'Playfair Display', Georgia, 'Times New Roman', serif"
SANS = "-apple-system, 'Helvetica Neue', Arial, sans-serif"


def product_cell(name, handle, img):
    url = f"{STORE}/products/{handle}"
    name_e = html.escape(name)
    return f"""              <td width="132" valign="top" style="padding:2px;">
                <a href="{url}" target="_blank" style="text-decoration:none;color:{INK};">
                  <img src="{img}" width="128" alt="{name_e}" style="display:block;width:128px;height:128px;object-fit:cover;border:1px solid {LINE};border-radius:3px;background:#fff;">
                  <div style="font-family:{SANS};font-size:11px;line-height:1.3;color:{INK};padding:5px 2px 0;text-align:center;">{name_e}</div>
                </a>
              </td>"""


def vendor_block(v):
    # Logo header: image OR live text wordmark.
    if v.get("wordmark"):
        header = (f'<div style="font-family:{SERIF};font-weight:600;font-size:30px;'
                  f'letter-spacing:.3px;color:{INK};">{html.escape(v["wordmark"])}</div>')
    else:
        w = v.get("logo_w", 200)
        header = (f'<img src="{v["logo"]}" alt="{html.escape(v["name"])}" '
                  f'width="{w}" style="display:inline-block;width:{w}px;max-width:82%;height:auto;border:0;">')
    cells = "\n".join(product_cell(*p) for p in v["products"])
    coll = f"{STORE}/collections/{v['collection']}"
    tagline = html.escape(v.get("tagline", ""))
    return f"""
        <!-- {html.escape(v['name'])} -->
        <tr><td style="padding:16px 24px 4px;text-align:center;">
          {header}
          <div style="font-family:{SANS};font-size:12px;color:{MUTED};padding-top:6px;">{tagline}</div>
        </td></tr>
        <tr><td style="padding:6px 10px 0;">
          <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"><tr>
{cells}
          </tr></table>
        </td></tr>
        <tr><td style="padding:12px 24px 6px;text-align:center;">
          <div style="font-family:{SANS};font-size:12px;color:{MUTED};padding-bottom:12px;">Order any pattern as a <strong style="color:{INK};">sample</strong>.</div>
          <a href="{coll}" target="_blank" style="display:inline-block;font-family:{SANS};font-size:12px;letter-spacing:1.2px;text-transform:uppercase;color:{INK};text-decoration:none;border:1px solid {INK};padding:10px 26px;border-radius:2px;">Shop {html.escape(v['name'])} &rarr;</a>
        </td></tr>
        <tr><td style="padding:22px 24px 0;"><div style="border-top:1px solid {LINE};font-size:0;line-height:0;">&nbsp;</div></td></tr>"""


def build():
    rows = "".join(vendor_block(v) for v in VENDORS)
    doc = f"""<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>New — Houses We Represent</title></head>
<body style="margin:0;padding:0;background:{CREAM};">
<div style="display:none;max-height:0;overflow:hidden;opacity:0;">Six ateliers we represent — Phillipe Romano, Thibaut, Cole &amp; Son, Brunschwig &amp; Fils, Designers Guild &amp; Osborne &amp; Little. Order any pattern as a sample.</div>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:{CREAM};">
<tr><td align="center" style="padding:24px 12px;">
  <table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;background:#ffffff;border:1px solid {LINE};">

    <!-- Masthead -->
    <tr><td style="padding:24px 24px 2px;text-align:center;">
      <a href="{NEW_ARRIVALS}" target="_blank" style="text-decoration:none;border:0;">
        <img src="{DW_LOGO}" alt="Designer Wallcoverings" width="188" style="display:inline-block;width:188px;max-width:62%;height:auto;border:0;">
      </a>
    </td></tr>
    <tr><td style="padding:2px 24px 6px;text-align:center;">
      <a href="{NEW_ARRIVALS}" target="_blank" style="font-family:{SANS};font-size:11px;letter-spacing:3px;text-transform:uppercase;color:{MUTED};text-decoration:none;">Designer Wallcoverings &nbsp;&middot;&nbsp; est. 2003 &nbsp;&middot;&nbsp; to the trade</a>
    </td></tr>
    <tr><td style="padding:6px 24px 4px;text-align:center;">
      <div style="font-family:{SERIF};font-size:32px;line-height:1.15;color:{INK};">The Houses We Represent</div>
      <div style="font-family:{SANS};font-size:13px;line-height:1.55;color:{MUTED};padding:10px 20px 0;">Six ateliers, on our shelves. A few new arrivals from each &mdash; order any pattern as a <strong style="color:{INK};">sample</strong>, shipped to your studio the same week.</div>
    </td></tr>
    <tr><td style="padding:18px 24px 0;"><div style="border-top:2px solid {INK};font-size:0;line-height:0;">&nbsp;</div></td></tr>
{rows}

    <!-- Footer -->
    <tr><td style="padding:24px;text-align:center;">
      <div style="font-family:{SERIF};font-size:16px;color:{INK};">Designer Wallcoverings</div>
      <div style="font-family:{SANS};font-size:11px;line-height:1.7;color:{MUTED};padding-top:8px;">
        To the trade &middot; Los Angeles, since 2003<br>
        15442 Ventura Blvd &middot; Sherman Oaks, CA 91403 &middot; 1-888-373-4564<br>
        <a href="mailto:info@designerwallcoverings.com" style="color:{MUTED};">info@designerwallcoverings.com</a> &middot;
        <a href="{STORE}" target="_blank" style="color:{MUTED};">designerwallcoverings.com</a>
      </div>
      <div style="font-family:{SANS};font-size:10px;line-height:1.6;color:#b3aa9f;padding-top:14px;">
        You're receiving this because you've shopped or registered with Designer Wallcoverings.<br>
        Unsubscribe &amp; preference links are added automatically below by Constant Contact.
      </div>
    </td></tr>

  </table>
</td></tr>
</table>
</body></html>"""
    out = pathlib.Path(__file__).parent / "mailer.html"
    out.write_text(doc, encoding="utf-8")
    print(f"wrote {out}  ({len(doc):,} bytes)  vendors={len(VENDORS)} products={sum(len(v['products']) for v in VENDORS)}")


if __name__ == "__main__":
    build()